@feelyourprotocol/vm 8141.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +583 -0
- package/dist/cjs/bloom/index.d.ts +29 -0
- package/dist/cjs/bloom/index.d.ts.map +1 -0
- package/dist/cjs/bloom/index.js +76 -0
- package/dist/cjs/bloom/index.js.map +1 -0
- package/dist/cjs/buildBlock.d.ts +118 -0
- package/dist/cjs/buildBlock.d.ts.map +1 -0
- package/dist/cjs/buildBlock.js +363 -0
- package/dist/cjs/buildBlock.js.map +1 -0
- package/dist/cjs/constructors.d.ts +9 -0
- package/dist/cjs/constructors.d.ts.map +1 -0
- package/dist/cjs/constructors.js +75 -0
- package/dist/cjs/constructors.js.map +1 -0
- package/dist/cjs/emitEVMProfile.d.ts +9 -0
- package/dist/cjs/emitEVMProfile.d.ts.map +1 -0
- package/dist/cjs/emitEVMProfile.js +130 -0
- package/dist/cjs/emitEVMProfile.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/params.d.ts +3 -0
- package/dist/cjs/params.d.ts.map +1 -0
- package/dist/cjs/params.js +105 -0
- package/dist/cjs/params.js.map +1 -0
- package/dist/cjs/requests.d.ts +11 -0
- package/dist/cjs/requests.d.ts.map +1 -0
- package/dist/cjs/requests.js +208 -0
- package/dist/cjs/requests.js.map +1 -0
- package/dist/cjs/runBlock.d.ts +35 -0
- package/dist/cjs/runBlock.d.ts.map +1 -0
- package/dist/cjs/runBlock.js +797 -0
- package/dist/cjs/runBlock.js.map +1 -0
- package/dist/cjs/runFrameTx.d.ts +18 -0
- package/dist/cjs/runFrameTx.d.ts.map +1 -0
- package/dist/cjs/runFrameTx.js +313 -0
- package/dist/cjs/runFrameTx.js.map +1 -0
- package/dist/cjs/runTx.d.ts +18 -0
- package/dist/cjs/runTx.d.ts.map +1 -0
- package/dist/cjs/runTx.js +900 -0
- package/dist/cjs/runTx.js.map +1 -0
- package/dist/cjs/types.d.ts +452 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/vm.d.ts +75 -0
- package/dist/cjs/vm.d.ts.map +1 -0
- package/dist/cjs/vm.js +111 -0
- package/dist/cjs/vm.js.map +1 -0
- package/dist/esm/bloom/index.d.ts +29 -0
- package/dist/esm/bloom/index.d.ts.map +1 -0
- package/dist/esm/bloom/index.js +72 -0
- package/dist/esm/bloom/index.js.map +1 -0
- package/dist/esm/buildBlock.d.ts +118 -0
- package/dist/esm/buildBlock.d.ts.map +1 -0
- package/dist/esm/buildBlock.js +358 -0
- package/dist/esm/buildBlock.js.map +1 -0
- package/dist/esm/constructors.d.ts +9 -0
- package/dist/esm/constructors.d.ts.map +1 -0
- package/dist/esm/constructors.js +72 -0
- package/dist/esm/constructors.js.map +1 -0
- package/dist/esm/emitEVMProfile.d.ts +9 -0
- package/dist/esm/emitEVMProfile.d.ts.map +1 -0
- package/dist/esm/emitEVMProfile.js +127 -0
- package/dist/esm/emitEVMProfile.js.map +1 -0
- package/dist/esm/index.d.ts +11 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/params.d.ts +3 -0
- package/dist/esm/params.d.ts.map +1 -0
- package/dist/esm/params.js +102 -0
- package/dist/esm/params.js.map +1 -0
- package/dist/esm/requests.d.ts +11 -0
- package/dist/esm/requests.d.ts.map +1 -0
- package/dist/esm/requests.js +204 -0
- package/dist/esm/requests.js.map +1 -0
- package/dist/esm/runBlock.d.ts +35 -0
- package/dist/esm/runBlock.d.ts.map +1 -0
- package/dist/esm/runBlock.js +790 -0
- package/dist/esm/runBlock.js.map +1 -0
- package/dist/esm/runFrameTx.d.ts +18 -0
- package/dist/esm/runFrameTx.d.ts.map +1 -0
- package/dist/esm/runFrameTx.js +310 -0
- package/dist/esm/runFrameTx.js.map +1 -0
- package/dist/esm/runTx.d.ts +18 -0
- package/dist/esm/runTx.d.ts.map +1 -0
- package/dist/esm/runTx.js +896 -0
- package/dist/esm/runTx.js.map +1 -0
- package/dist/esm/types.d.ts +452 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/vm.d.ts +75 -0
- package/dist/esm/vm.d.ts.map +1 -0
- package/dist/esm/vm.js +107 -0
- package/dist/esm/vm.js.map +1 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
- package/package.json +117 -0
- package/src/bloom/index.ts +83 -0
- package/src/buildBlock.ts +470 -0
- package/src/constructors.ts +91 -0
- package/src/emitEVMProfile.ts +151 -0
- package/src/index.ts +10 -0
- package/src/params.ts +104 -0
- package/src/requests.ts +293 -0
- package/src/runBlock.ts +1022 -0
- package/src/runFrameTx.ts +411 -0
- package/src/runTx.ts +1203 -0
- package/src/types.ts +511 -0
- package/src/vm.ts +147 -0
package/package.json
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@feelyourprotocol/vm",
|
|
3
|
+
"version": "8141.0.0",
|
|
4
|
+
"description": "An Ethereum VM implementation",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ethereum",
|
|
7
|
+
"VM"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/vm#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aissue+label%3A%22package%3A+vm%22"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/ethereumjs/ethereumjs-monorepo.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "MPL-2.0",
|
|
18
|
+
"author": "EthereumJS Team",
|
|
19
|
+
"contributors": [
|
|
20
|
+
"mjbecze <mjbecze@gmail.com>",
|
|
21
|
+
"Alex Beregszaszi <alex@rtfs.hu>"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"main": "dist/cjs/index.js",
|
|
26
|
+
"module": "dist/esm/index.js",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": {
|
|
30
|
+
"typescript": "./src/index.ts",
|
|
31
|
+
"default": "./dist/esm/index.js"
|
|
32
|
+
},
|
|
33
|
+
"require": "./dist/cjs/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"benchmarks": "node --max-old-space-size=4096 ./benchmarks/run.js benchmarks mainnetBlocks:10",
|
|
42
|
+
"biome": "npx @biomejs/biome check",
|
|
43
|
+
"biome:fix": "npx @biomejs/biome check --write",
|
|
44
|
+
"build": "../../config/cli/ts-build.sh",
|
|
45
|
+
"build:benchmarks": "npm run build && tsc -p tsconfig.benchmarks.json",
|
|
46
|
+
"clean": "../../config/cli/clean-package.sh",
|
|
47
|
+
"coverage": "DEBUG=ethjs npx vitest run -c ./vitest.config.coverage.ts",
|
|
48
|
+
"docs:build": "typedoc --options typedoc.mjs",
|
|
49
|
+
"examples": "tsx ../../scripts/examples-runner.ts -- vm",
|
|
50
|
+
"examples:build": "npx embedme README.md",
|
|
51
|
+
"formatTest": "node ./scripts/formatTest",
|
|
52
|
+
"lint": "npm run biome && eslint --config ./eslint.config.mjs .",
|
|
53
|
+
"lint:fix": "npm run biome:fix && eslint --fix --config ./eslint.config.mjs .",
|
|
54
|
+
"prepublishOnly": "../../config/cli/prepublish.sh && npm run test:buildIntegrity",
|
|
55
|
+
"profiling": "0x ./benchmarks/run.js profiling",
|
|
56
|
+
"test": "echo \"[INFO] Generic test cmd not used. See package.json for more specific test run cmds.\"",
|
|
57
|
+
"test:API": "npx vitest run -c ./vitest.config.ts ./test/api/",
|
|
58
|
+
"test:browser": "npx vitest run --config=./vitest.config.browser.mts",
|
|
59
|
+
"test:blockchain": "tsx ./test/tester/vitest-wrapper-blockchain.ts",
|
|
60
|
+
"test:blockchain:allForks": "npm run test:blockchain:newForks && npm run test:blockchain:oldForks && npm run test:blockchain:transitionForks",
|
|
61
|
+
"test:blockchain:newForks": "echo 'Prague' | xargs -n1 | xargs -I {} sh -c 'VITE_FORK={} VITE_VERIFY_TEST_AMOUNT_ALL_TESTS=1 npx vitest test/tester/blockchain.spec.ts'",
|
|
62
|
+
"test:blockchain:oldForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin London Paris Shanghai Cancun' | xargs -n1 | xargs -I {} sh -c 'VITE_FORK={} VITE_VERIFY_TEST_AMOUNT_ALL_TESTS=1 npx vitest test/tester/blockchain.spec.ts'",
|
|
63
|
+
"test:blockchain:transitionForks": "echo 'ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5 BerlinToLondonAt5' | xargs -n1 | xargs -I {} sh -c 'VITE_FORK={} VITE_VERIFY_TEST_AMOUNT_ALL_TESTS=1 npx vitest test/tester/blockchain.spec.ts'",
|
|
64
|
+
"test:blockchain:buildIntegrity": "npm run test:blockchain -- --file='randomStatetest303'",
|
|
65
|
+
"test:buildIntegrity": "npm run test:state -- --test='stackOverflow'",
|
|
66
|
+
"test:est:stable:state": "TEST_PATH=../execution-spec-tests/stable/state_tests npx vitest run test/tester/executionSpecState.test.ts",
|
|
67
|
+
"test:est:stable:blockchain": "TEST_PATH=../execution-spec-tests/stable/blockchain_tests npx vitest run test/tester/executionSpecBlockchain.test.ts",
|
|
68
|
+
"test:est:dev:state": "TEST_PATH=../execution-spec-tests/dev/state_tests npx vitest run test/tester/executionSpecState.test.ts",
|
|
69
|
+
"test:est:dev:blockchain": "npm run test:est:dev:blockchain:v510",
|
|
70
|
+
"test:est:dev:blockchain:v510": "TEST_PATH=../execution-spec-tests/dev/blockchain_tests/amsterdam/v510_mixed_with_other_eips npx vitest run test/tester/executionSpecBlockchain.test.ts",
|
|
71
|
+
"test:est:dev:blockchain:v301": "TEST_PATH=../execution-spec-tests/dev/blockchain_tests/amsterdam/v301_single_bal_no_bal_defs npx vitest run test/tester/executionSpecBlockchain.test.ts",
|
|
72
|
+
"test:est:dev:blockchain:v200": "TEST_PATH=../execution-spec-tests/dev/blockchain_tests/amsterdam/v200_bal_defs_somewhat_outdated npx vitest run test/tester/executionSpecBlockchain.test.ts",
|
|
73
|
+
"test:state": "tsx ./test/tester/vitest-wrapper.ts",
|
|
74
|
+
"test:state:allForks": "npm run test:state:newForks && npm run test:state:oldForks && npm run test:state:transitionForks",
|
|
75
|
+
"test:state:newForks": "echo 'Prague' | xargs -n1 | xargs -I {} sh -c 'VITE_FORK={} VITE_VERIFY_TEST_AMOUNT_ALL_TESTS=1 npx vitest test/tester/state.spec.ts'",
|
|
76
|
+
"test:state:oldForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin London Paris Shanghai Cancun' | xargs -n1 | xargs -I {} sh -c 'VITE_FORK={} VITE_VERIFY_TEST_AMOUNT_ALL_TESTS=1 npx vitest test/tester/state.spec.ts'",
|
|
77
|
+
"test:state:transitionForks": "echo 'ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5 BerlinToLondonAt5' | xargs -n1 | xargs -I {} sh -c 'VITE_FORK={} VITE_VERIFY_TEST_AMOUNT_ALL_TESTS=1 npx vitest test/tester/state.spec.ts'",
|
|
78
|
+
"test:state:slow": "VITE_RUN_SKIPPED=slow npx vitest test/tester/state.spec.ts",
|
|
79
|
+
"test:analysis:report": "npx tsx ./test/tester/util/testAnalysisReportAI.ts",
|
|
80
|
+
"tsc": "../../config/cli/ts-compile.sh"
|
|
81
|
+
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"@feelyourprotocol/block": "^8141.0.0",
|
|
84
|
+
"@feelyourprotocol/common": "^8141.0.0",
|
|
85
|
+
"@feelyourprotocol/evm": "^8141.0.0",
|
|
86
|
+
"@feelyourprotocol/mpt": "^8141.0.0",
|
|
87
|
+
"@feelyourprotocol/rlp": "^8141.0.0",
|
|
88
|
+
"@feelyourprotocol/statemanager": "^8141.0.0",
|
|
89
|
+
"@feelyourprotocol/tx": "^8141.0.0",
|
|
90
|
+
"@feelyourprotocol/util": "^8141.0.0",
|
|
91
|
+
"@noble/hashes": "^2.0.1",
|
|
92
|
+
"debug": "^4.4.0",
|
|
93
|
+
"eventemitter3": "^5.0.1"
|
|
94
|
+
},
|
|
95
|
+
"devDependencies": {
|
|
96
|
+
"@feelyourprotocol/blockchain": "^8141.0.0",
|
|
97
|
+
"@ethereumjs/ethash": "^10.0.0",
|
|
98
|
+
"@ethereumjs/testdata": "1.0.0",
|
|
99
|
+
"@noble/curves": "^2.0.1",
|
|
100
|
+
"@paulmillr/trusted-setups": "^0.2.0",
|
|
101
|
+
"@types/benchmark": "^2.1.5",
|
|
102
|
+
"@types/debug": "^4.1.12",
|
|
103
|
+
"@types/minimist": "^1.2.5",
|
|
104
|
+
"@types/node-dir": "^0.0.37",
|
|
105
|
+
"benchmark": "^2.1.4",
|
|
106
|
+
"mcl-wasm": "^1.8.0",
|
|
107
|
+
"micro-eth-signer": "^0.15.0",
|
|
108
|
+
"minimist": "^1.2.8",
|
|
109
|
+
"node-dir": "^0.1.17",
|
|
110
|
+
"solc": "^0.8.28",
|
|
111
|
+
"viem": "^2.38.6",
|
|
112
|
+
"yargs": "^17.7.2"
|
|
113
|
+
},
|
|
114
|
+
"engines": {
|
|
115
|
+
"node": ">=20"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { EthereumJSErrorWithoutCode } from '@feelyourprotocol/util'
|
|
2
|
+
import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
3
|
+
|
|
4
|
+
import type { Common } from '@feelyourprotocol/common'
|
|
5
|
+
|
|
6
|
+
const BYTE_SIZE = 256
|
|
7
|
+
|
|
8
|
+
export class Bloom {
|
|
9
|
+
bitvector: Uint8Array
|
|
10
|
+
keccakFunction: (msg: Uint8Array) => Uint8Array
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents a Bloom filter.
|
|
14
|
+
*/
|
|
15
|
+
constructor(bitvector?: Uint8Array, common?: Common) {
|
|
16
|
+
if (common?.customCrypto.keccak256 !== undefined) {
|
|
17
|
+
this.keccakFunction = common.customCrypto.keccak256
|
|
18
|
+
} else {
|
|
19
|
+
this.keccakFunction = keccak_256
|
|
20
|
+
}
|
|
21
|
+
if (!bitvector) {
|
|
22
|
+
this.bitvector = new Uint8Array(BYTE_SIZE)
|
|
23
|
+
} else {
|
|
24
|
+
if (bitvector.length !== BYTE_SIZE)
|
|
25
|
+
throw EthereumJSErrorWithoutCode('bitvectors must be 2048 bits long')
|
|
26
|
+
this.bitvector = bitvector
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Adds an element to a bit vector of a 64 byte bloom filter.
|
|
32
|
+
* @param e - The element to add
|
|
33
|
+
*/
|
|
34
|
+
add(e: Uint8Array) {
|
|
35
|
+
e = this.keccakFunction(e)
|
|
36
|
+
const mask = 2047 // binary 11111111111
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const first2bytes = new DataView(e.buffer).getUint16(i * 2)
|
|
40
|
+
const loc = mask & first2bytes
|
|
41
|
+
const byteLoc = loc >> 3
|
|
42
|
+
const bitLoc = 1 << (loc % 8)
|
|
43
|
+
this.bitvector[BYTE_SIZE - byteLoc - 1] |= bitLoc
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if an element is in the bloom.
|
|
49
|
+
* @param e - The element to check
|
|
50
|
+
*/
|
|
51
|
+
check(e: Uint8Array): boolean {
|
|
52
|
+
e = this.keccakFunction(e)
|
|
53
|
+
const mask = 2047 // binary 11111111111
|
|
54
|
+
let match = true
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < 3 && match; i++) {
|
|
57
|
+
const first2bytes = new DataView(e.buffer).getUint16(i * 2)
|
|
58
|
+
const loc = mask & first2bytes
|
|
59
|
+
const byteLoc = loc >> 3
|
|
60
|
+
const bitLoc = 1 << (loc % 8)
|
|
61
|
+
match = (this.bitvector[BYTE_SIZE - byteLoc - 1] & bitLoc) !== 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Boolean(match)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks if multiple topics are in a bloom.
|
|
69
|
+
* @returns `true` if every topic is in the bloom
|
|
70
|
+
*/
|
|
71
|
+
multiCheck(topics: Uint8Array[]): boolean {
|
|
72
|
+
return topics.every((t: Uint8Array) => this.check(t))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Bitwise or blooms together.
|
|
77
|
+
*/
|
|
78
|
+
or(bloom: Bloom) {
|
|
79
|
+
for (let i = 0; i <= BYTE_SIZE; i++) {
|
|
80
|
+
this.bitvector[i] = this.bitvector[i] | bloom.bitvector[i]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBlock,
|
|
3
|
+
createSealedCliqueBlock,
|
|
4
|
+
genRequestsRoot,
|
|
5
|
+
genTransactionsTrieRoot,
|
|
6
|
+
genWithdrawalsTrieRoot,
|
|
7
|
+
} from '@feelyourprotocol/block'
|
|
8
|
+
import { ConsensusType, Hardfork } from '@feelyourprotocol/common'
|
|
9
|
+
import { MerklePatriciaTrie } from '@feelyourprotocol/mpt'
|
|
10
|
+
import { RLP } from '@feelyourprotocol/rlp'
|
|
11
|
+
import {
|
|
12
|
+
Blob4844Tx,
|
|
13
|
+
NetworkWrapperType,
|
|
14
|
+
createMinimal4844TxFromNetworkWrapper,
|
|
15
|
+
} from '@feelyourprotocol/tx'
|
|
16
|
+
import {
|
|
17
|
+
Address,
|
|
18
|
+
BIGINT_0,
|
|
19
|
+
BIGINT_1,
|
|
20
|
+
BIGINT_2,
|
|
21
|
+
EthereumJSErrorWithoutCode,
|
|
22
|
+
GWEI_TO_WEI,
|
|
23
|
+
KECCAK256_RLP,
|
|
24
|
+
TypeOutput,
|
|
25
|
+
createWithdrawal,
|
|
26
|
+
createZeroAddress,
|
|
27
|
+
toBytes,
|
|
28
|
+
toType,
|
|
29
|
+
} from '@feelyourprotocol/util'
|
|
30
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
31
|
+
|
|
32
|
+
import { Bloom } from './bloom/index.ts'
|
|
33
|
+
import { runTx } from './index.ts'
|
|
34
|
+
import { accumulateRequests } from './requests.ts'
|
|
35
|
+
import {
|
|
36
|
+
accumulateParentBeaconBlockRoot,
|
|
37
|
+
accumulateParentBlockHash,
|
|
38
|
+
calculateMinerReward,
|
|
39
|
+
encodeReceipt,
|
|
40
|
+
rewardAccount,
|
|
41
|
+
} from './runBlock.ts'
|
|
42
|
+
|
|
43
|
+
import type { Block, HeaderData } from '@feelyourprotocol/block'
|
|
44
|
+
import type { TypedTransaction } from '@feelyourprotocol/tx'
|
|
45
|
+
import type { Withdrawal } from '@feelyourprotocol/util'
|
|
46
|
+
import type { BuildBlockOpts, BuilderOpts, RunTxResult, SealBlockOpts } from './types.ts'
|
|
47
|
+
import type { VM } from './vm.ts'
|
|
48
|
+
|
|
49
|
+
export type BuildStatus = (typeof BuildStatus)[keyof typeof BuildStatus]
|
|
50
|
+
export const BuildStatus = {
|
|
51
|
+
Reverted: 'reverted',
|
|
52
|
+
Build: 'build',
|
|
53
|
+
Pending: 'pending',
|
|
54
|
+
} as const
|
|
55
|
+
|
|
56
|
+
type BlockStatus =
|
|
57
|
+
| { status: typeof BuildStatus.Pending | typeof BuildStatus.Reverted }
|
|
58
|
+
| { status: typeof BuildStatus.Build; block: Block }
|
|
59
|
+
|
|
60
|
+
export class BlockBuilder {
|
|
61
|
+
/**
|
|
62
|
+
* The cumulative gas used by the transactions added to the block.
|
|
63
|
+
*/
|
|
64
|
+
gasUsed = BIGINT_0
|
|
65
|
+
/**
|
|
66
|
+
* The cumulative blob gas used by the blobs in a block
|
|
67
|
+
*/
|
|
68
|
+
blobGasUsed = BIGINT_0
|
|
69
|
+
/**
|
|
70
|
+
* Value of the block, represented by the final transaction fees
|
|
71
|
+
* accruing to the miner.
|
|
72
|
+
*/
|
|
73
|
+
private _minerValue = BIGINT_0
|
|
74
|
+
|
|
75
|
+
private readonly vm: VM
|
|
76
|
+
private blockOpts: BuilderOpts
|
|
77
|
+
private headerData: HeaderData
|
|
78
|
+
private transactions: TypedTransaction[] = []
|
|
79
|
+
private transactionResults: RunTxResult[] = []
|
|
80
|
+
private withdrawals?: Withdrawal[]
|
|
81
|
+
private checkpointed = false
|
|
82
|
+
private blockStatus: BlockStatus = { status: BuildStatus.Pending }
|
|
83
|
+
|
|
84
|
+
get transactionReceipts() {
|
|
85
|
+
return this.transactionResults.map((result) => result.receipt)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get minerValue() {
|
|
89
|
+
return this._minerValue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
constructor(vm: VM, opts: BuildBlockOpts) {
|
|
93
|
+
this.vm = vm
|
|
94
|
+
this.blockOpts = { putBlockIntoBlockchain: true, ...opts.blockOpts, common: this.vm.common }
|
|
95
|
+
|
|
96
|
+
this.headerData = {
|
|
97
|
+
...opts.headerData,
|
|
98
|
+
parentHash: opts.headerData?.parentHash ?? opts.parentBlock.hash(),
|
|
99
|
+
number: opts.headerData?.number ?? opts.parentBlock.header.number + BIGINT_1,
|
|
100
|
+
gasLimit: opts.headerData?.gasLimit ?? opts.parentBlock.header.gasLimit,
|
|
101
|
+
timestamp: opts.headerData?.timestamp ?? Math.round(Date.now() / 1000),
|
|
102
|
+
}
|
|
103
|
+
this.withdrawals = opts.withdrawals?.map(createWithdrawal)
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
this.vm.common.isActivatedEIP(1559) &&
|
|
107
|
+
typeof this.headerData.baseFeePerGas === 'undefined'
|
|
108
|
+
) {
|
|
109
|
+
if (this.headerData.number === vm.common.hardforkBlock(Hardfork.London)) {
|
|
110
|
+
this.headerData.baseFeePerGas = vm.common.param('initialBaseFee')
|
|
111
|
+
} else {
|
|
112
|
+
this.headerData.baseFeePerGas = opts.parentBlock.header.calcNextBaseFee()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof this.headerData.gasLimit === 'undefined') {
|
|
117
|
+
if (this.headerData.number === vm.common.hardforkBlock(Hardfork.London)) {
|
|
118
|
+
this.headerData.gasLimit = opts.parentBlock.header.gasLimit * BIGINT_2
|
|
119
|
+
} else {
|
|
120
|
+
this.headerData.gasLimit = opts.parentBlock.header.gasLimit
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
this.vm.common.isActivatedEIP(4844) &&
|
|
126
|
+
typeof this.headerData.excessBlobGas === 'undefined'
|
|
127
|
+
) {
|
|
128
|
+
this.headerData.excessBlobGas = opts.parentBlock.header.calcNextExcessBlobGas(this.vm.common)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Throws if the block has already been built or reverted.
|
|
134
|
+
*/
|
|
135
|
+
private checkStatus() {
|
|
136
|
+
if (this.blockStatus.status === BuildStatus.Build) {
|
|
137
|
+
throw EthereumJSErrorWithoutCode('Block has already been built')
|
|
138
|
+
}
|
|
139
|
+
if (this.blockStatus.status === BuildStatus.Reverted) {
|
|
140
|
+
throw EthereumJSErrorWithoutCode('State has already been reverted')
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public getStatus(): BlockStatus {
|
|
145
|
+
return this.blockStatus
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Calculates and returns the transactionsTrie for the block.
|
|
150
|
+
*/
|
|
151
|
+
public async transactionsTrie() {
|
|
152
|
+
return genTransactionsTrieRoot(
|
|
153
|
+
this.transactions,
|
|
154
|
+
new MerklePatriciaTrie({ common: this.vm.common }),
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Calculates and returns the logs bloom for the block.
|
|
160
|
+
*/
|
|
161
|
+
public logsBloom() {
|
|
162
|
+
const bloom = new Bloom(undefined, this.vm.common)
|
|
163
|
+
for (const txResult of this.transactionResults) {
|
|
164
|
+
// Combine blooms via bitwise OR
|
|
165
|
+
bloom.or(txResult.bloom)
|
|
166
|
+
}
|
|
167
|
+
return bloom.bitvector
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Calculates and returns the receiptTrie for the block.
|
|
172
|
+
*/
|
|
173
|
+
public async receiptTrie() {
|
|
174
|
+
if (this.transactionResults.length === 0) {
|
|
175
|
+
return KECCAK256_RLP
|
|
176
|
+
}
|
|
177
|
+
const receiptTrie = new MerklePatriciaTrie({ common: this.vm.common })
|
|
178
|
+
for (const [i, txResult] of this.transactionResults.entries()) {
|
|
179
|
+
const tx = this.transactions[i]
|
|
180
|
+
const encodedReceipt = encodeReceipt(txResult.receipt, tx.type)
|
|
181
|
+
await receiptTrie.put(RLP.encode(i), encodedReceipt)
|
|
182
|
+
}
|
|
183
|
+
return receiptTrie.root()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Adds the block miner reward to the coinbase account.
|
|
188
|
+
*/
|
|
189
|
+
private async rewardMiner() {
|
|
190
|
+
const minerReward = this.vm.common.param('minerReward')
|
|
191
|
+
const reward = calculateMinerReward(minerReward, 0)
|
|
192
|
+
const coinbase =
|
|
193
|
+
this.headerData.coinbase !== undefined
|
|
194
|
+
? new Address(toBytes(this.headerData.coinbase))
|
|
195
|
+
: createZeroAddress()
|
|
196
|
+
await rewardAccount(this.vm.evm, coinbase, reward, this.vm.common)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Adds the withdrawal amount to the withdrawal address
|
|
201
|
+
*/
|
|
202
|
+
private async processWithdrawals() {
|
|
203
|
+
for (const withdrawal of this.withdrawals ?? []) {
|
|
204
|
+
const { address, amount } = withdrawal
|
|
205
|
+
// If there is no amount to add, skip touching the account
|
|
206
|
+
// as per the implementation of other clients geth/nethermind
|
|
207
|
+
// although this should never happen as no withdrawals with 0
|
|
208
|
+
// amount should ever land up here.
|
|
209
|
+
if (amount === BIGINT_0) continue
|
|
210
|
+
// Withdrawal amount is represented in Gwei so needs to be
|
|
211
|
+
// converted to wei
|
|
212
|
+
await rewardAccount(this.vm.evm, address, amount * GWEI_TO_WEI, this.vm.common)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Run and add a transaction to the block being built.
|
|
218
|
+
* Please note that this modifies the state of the VM.
|
|
219
|
+
* Throws if the transaction's gasLimit is greater than
|
|
220
|
+
* the remaining gas in the block.
|
|
221
|
+
*/
|
|
222
|
+
async addTransaction(
|
|
223
|
+
tx: TypedTransaction,
|
|
224
|
+
{
|
|
225
|
+
skipHardForkValidation,
|
|
226
|
+
allowNoBlobs,
|
|
227
|
+
}: { skipHardForkValidation?: boolean; allowNoBlobs?: boolean } = {},
|
|
228
|
+
) {
|
|
229
|
+
this.checkStatus()
|
|
230
|
+
|
|
231
|
+
if (!this.checkpointed) {
|
|
232
|
+
await this.vm.evm.journal.checkpoint()
|
|
233
|
+
this.checkpointed = true
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// According to the Yellow Paper, a transaction's gas limit
|
|
237
|
+
// cannot be greater than the remaining gas in the block
|
|
238
|
+
const blockGasLimit = toType(this.headerData.gasLimit, TypeOutput.BigInt)
|
|
239
|
+
|
|
240
|
+
const blobGasPerBlob = this.vm.common.param('blobGasPerBlob')
|
|
241
|
+
|
|
242
|
+
const blockGasRemaining = blockGasLimit - this.gasUsed
|
|
243
|
+
if (tx.gasLimit > blockGasRemaining) {
|
|
244
|
+
throw EthereumJSErrorWithoutCode(
|
|
245
|
+
'tx has a higher gas limit than the remaining gas in the block',
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
let blobGasUsed = undefined
|
|
249
|
+
if (tx instanceof Blob4844Tx) {
|
|
250
|
+
const { maxBlobGasPerBlock: blobGasLimit } = this.vm.common.getBlobGasSchedule()
|
|
251
|
+
if (
|
|
252
|
+
tx.networkWrapperVersion === NetworkWrapperType.EIP4844 &&
|
|
253
|
+
this.vm.common.isActivatedEIP(7594)
|
|
254
|
+
) {
|
|
255
|
+
throw Error('eip4844 blob transaction for eip7594 activated fork')
|
|
256
|
+
} else if (
|
|
257
|
+
tx.networkWrapperVersion === NetworkWrapperType.EIP7594 &&
|
|
258
|
+
!this.vm.common.isActivatedEIP(7594)
|
|
259
|
+
) {
|
|
260
|
+
throw Error('eip7594 blob transaction but eip not yet activated')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (this.blockOpts.common?.isActivatedEIP(4844) === false) {
|
|
264
|
+
throw Error('eip4844 not activated yet for adding a blob transaction')
|
|
265
|
+
}
|
|
266
|
+
const blobTx = tx as Blob4844Tx
|
|
267
|
+
|
|
268
|
+
// Guard against the case if a tx came into the pool without blobs i.e. network wrapper payload
|
|
269
|
+
if (blobTx.blobs === undefined) {
|
|
270
|
+
// TODO: verify if we want this, do we want to allow the block builder to accept blob txs without the actual blobs?
|
|
271
|
+
// (these must have at least one `blobVersionedHashes`, this is verified at tx-level)
|
|
272
|
+
if (allowNoBlobs !== true) {
|
|
273
|
+
throw EthereumJSErrorWithoutCode('blobs missing for 4844 transaction')
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (this.blobGasUsed + BigInt(blobTx.numBlobs()) * blobGasPerBlob > blobGasLimit) {
|
|
278
|
+
throw EthereumJSErrorWithoutCode('block blob gas limit reached')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
blobGasUsed = this.blobGasUsed
|
|
282
|
+
}
|
|
283
|
+
const header = {
|
|
284
|
+
...this.headerData,
|
|
285
|
+
gasUsed: this.gasUsed,
|
|
286
|
+
// correct excessBlobGas should already part of headerData used above
|
|
287
|
+
blobGasUsed,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const blockData = { header, transactions: this.transactions }
|
|
291
|
+
const block = createBlock(blockData, this.blockOpts)
|
|
292
|
+
|
|
293
|
+
const result = await runTx(this.vm, { tx, block, skipHardForkValidation })
|
|
294
|
+
|
|
295
|
+
// If tx is a blob transaction, remove blobs/kzg commitments before adding to block per EIP-4844
|
|
296
|
+
if (tx instanceof Blob4844Tx) {
|
|
297
|
+
const txData = tx as Blob4844Tx
|
|
298
|
+
this.blobGasUsed += BigInt(txData.blobVersionedHashes.length) * blobGasPerBlob
|
|
299
|
+
tx = createMinimal4844TxFromNetworkWrapper(txData, {
|
|
300
|
+
common: this.blockOpts.common,
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
this.transactions.push(tx)
|
|
304
|
+
this.transactionResults.push(result)
|
|
305
|
+
this.gasUsed += result.totalGasSpent
|
|
306
|
+
this._minerValue += result.minerValue
|
|
307
|
+
|
|
308
|
+
return result
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Reverts the checkpoint on the StateManager to reset the state from any transactions that have been run.
|
|
313
|
+
*/
|
|
314
|
+
async revert() {
|
|
315
|
+
if (this.checkpointed) {
|
|
316
|
+
await this.vm.evm.journal.revert()
|
|
317
|
+
this.checkpointed = false
|
|
318
|
+
}
|
|
319
|
+
this.blockStatus = { status: BuildStatus.Reverted }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* This method constructs the finalized block, including withdrawals and any CLRequests.
|
|
324
|
+
* It also:
|
|
325
|
+
* - Assigns the reward for miner (PoW)
|
|
326
|
+
* - Commits the checkpoint on the StateManager
|
|
327
|
+
* - Sets the tip of the VM's blockchain to this block
|
|
328
|
+
* For PoW, optionally seals the block with params `nonce` and `mixHash`,
|
|
329
|
+
* which is validated along with the block number and difficulty by ethash.
|
|
330
|
+
* For PoA, please pass `blockOption.cliqueSigner` into the buildBlock constructor,
|
|
331
|
+
* as the signer will be awarded the txs amount spent on gas as they are added.
|
|
332
|
+
*
|
|
333
|
+
* Note: we add CLRequests here because they can be generated at any time during the
|
|
334
|
+
* lifecycle of a pending block so need to be provided only when the block is finalized.
|
|
335
|
+
*/
|
|
336
|
+
async build(sealOpts?: SealBlockOpts) {
|
|
337
|
+
this.checkStatus()
|
|
338
|
+
const blockOpts = this.blockOpts
|
|
339
|
+
const consensusType = this.vm.common.consensusType()
|
|
340
|
+
|
|
341
|
+
if (consensusType === ConsensusType.ProofOfWork) {
|
|
342
|
+
await this.rewardMiner()
|
|
343
|
+
}
|
|
344
|
+
await this.processWithdrawals()
|
|
345
|
+
|
|
346
|
+
const transactionsTrie = await this.transactionsTrie()
|
|
347
|
+
const withdrawalsRoot = this.withdrawals
|
|
348
|
+
? await genWithdrawalsTrieRoot(
|
|
349
|
+
this.withdrawals,
|
|
350
|
+
new MerklePatriciaTrie({ common: this.vm.common }),
|
|
351
|
+
)
|
|
352
|
+
: undefined
|
|
353
|
+
const receiptTrie = await this.receiptTrie()
|
|
354
|
+
const logsBloom = this.logsBloom()
|
|
355
|
+
const gasUsed = this.gasUsed
|
|
356
|
+
// timestamp should already be set in constructor
|
|
357
|
+
const timestamp = this.headerData.timestamp ?? BIGINT_0
|
|
358
|
+
|
|
359
|
+
let blobGasUsed = undefined
|
|
360
|
+
if (this.vm.common.isActivatedEIP(4844)) {
|
|
361
|
+
blobGasUsed = this.blobGasUsed
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let requests
|
|
365
|
+
let requestsHash
|
|
366
|
+
if (this.vm.common.isActivatedEIP(7685)) {
|
|
367
|
+
const sha256Function = this.vm.common.customCrypto.sha256 ?? sha256
|
|
368
|
+
requests = await accumulateRequests(this.vm, this.transactionResults)
|
|
369
|
+
requestsHash = genRequestsRoot(requests, sha256Function)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// get stateRoot after all the accumulateRequests etc have been done
|
|
373
|
+
const stateRoot = await this.vm.stateManager.getStateRoot()
|
|
374
|
+
const headerData = {
|
|
375
|
+
...this.headerData,
|
|
376
|
+
stateRoot,
|
|
377
|
+
transactionsTrie,
|
|
378
|
+
withdrawalsRoot,
|
|
379
|
+
receiptTrie,
|
|
380
|
+
logsBloom,
|
|
381
|
+
gasUsed,
|
|
382
|
+
timestamp,
|
|
383
|
+
// correct excessBlobGas should already be part of headerData used above
|
|
384
|
+
blobGasUsed,
|
|
385
|
+
requestsHash,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (consensusType === ConsensusType.ProofOfWork) {
|
|
389
|
+
headerData.nonce = sealOpts?.nonce ?? headerData.nonce
|
|
390
|
+
headerData.mixHash = sealOpts?.mixHash ?? headerData.mixHash
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const blockData = {
|
|
394
|
+
header: headerData,
|
|
395
|
+
transactions: this.transactions,
|
|
396
|
+
withdrawals: this.withdrawals,
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let block
|
|
400
|
+
const cs = this.blockOpts.cliqueSigner
|
|
401
|
+
if (cs !== undefined) {
|
|
402
|
+
block = createSealedCliqueBlock(blockData, cs, this.blockOpts)
|
|
403
|
+
} else {
|
|
404
|
+
block = createBlock(blockData, blockOpts)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (this.blockOpts.putBlockIntoBlockchain === true) {
|
|
408
|
+
await this.vm.blockchain.putBlock(block)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
this.blockStatus = { status: BuildStatus.Build, block }
|
|
412
|
+
if (this.checkpointed) {
|
|
413
|
+
await this.vm.evm.journal.commit()
|
|
414
|
+
this.checkpointed = false
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return { block, requests }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async initState() {
|
|
421
|
+
if (this.vm.common.isActivatedEIP(4788)) {
|
|
422
|
+
if (!this.checkpointed) {
|
|
423
|
+
await this.vm.evm.journal.checkpoint()
|
|
424
|
+
this.checkpointed = true
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const { parentBeaconBlockRoot, timestamp } = this.headerData
|
|
428
|
+
// timestamp should already be set in constructor
|
|
429
|
+
const timestampBigInt = toType(timestamp ?? 0, TypeOutput.BigInt)
|
|
430
|
+
const parentBeaconBlockRootBuf =
|
|
431
|
+
toType(parentBeaconBlockRoot!, TypeOutput.Uint8Array) ?? new Uint8Array(32)
|
|
432
|
+
|
|
433
|
+
await accumulateParentBeaconBlockRoot(this.vm, parentBeaconBlockRootBuf, timestampBigInt)
|
|
434
|
+
}
|
|
435
|
+
if (this.vm.common.isActivatedEIP(2935)) {
|
|
436
|
+
if (!this.checkpointed) {
|
|
437
|
+
await this.vm.evm.journal.checkpoint()
|
|
438
|
+
this.checkpointed = true
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const { parentHash, number } = this.headerData
|
|
442
|
+
// timestamp should already be set in constructor
|
|
443
|
+
const numberBigInt = toType(number ?? 0, TypeOutput.BigInt)
|
|
444
|
+
const parentHashSanitized = toType(parentHash, TypeOutput.Uint8Array) ?? new Uint8Array(32)
|
|
445
|
+
|
|
446
|
+
await accumulateParentBlockHash(this.vm, numberBigInt, parentHashSanitized)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Build a block on top of the current state
|
|
453
|
+
* by adding one transaction at a time.
|
|
454
|
+
*
|
|
455
|
+
* Creates a checkpoint on the StateManager and modifies the state
|
|
456
|
+
* as transactions are run. The checkpoint is committed on {@link BlockBuilder.build}
|
|
457
|
+
* or discarded with {@link BlockBuilder.revert}.
|
|
458
|
+
*
|
|
459
|
+
* @param {VM} vm
|
|
460
|
+
* @param {BuildBlockOpts} opts
|
|
461
|
+
* @returns An instance of {@link BlockBuilder} with methods:
|
|
462
|
+
* - {@link BlockBuilder.addTransaction}
|
|
463
|
+
* - {@link BlockBuilder.build}
|
|
464
|
+
* - {@link BlockBuilder.revert}
|
|
465
|
+
*/
|
|
466
|
+
export async function buildBlock(vm: VM, opts: BuildBlockOpts): Promise<BlockBuilder> {
|
|
467
|
+
const blockBuilder = new BlockBuilder(vm, opts)
|
|
468
|
+
await blockBuilder.initState()
|
|
469
|
+
return blockBuilder
|
|
470
|
+
}
|