diff --git a/cmd/evm/README.md b/cmd/evm/README.md index 1a029ab7091a..6fd0abf65353 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -1,56 +1,196 @@ -## EVM state transition tool +# EVM tool + +The EVM tool provides a few useful subcommands to facilitate testing at the EVM +layer. + +* transition tool (`t8n`) : a stateless state transition utility +* transaction tool (`t9n`) : a transaction validation utility +* block builder tool (`b11r`): a block assembler utility + +## State transition tool (`t8n`) + The `evm t8n` tool is a stateless state transition utility. It is a utility which can 1. Take a prestate, including -- Accounts, -- Block context information, -- Previous blockshashes (*optional) + - Accounts, + - Block context information, + - Previous blockshashes (*optional) 2. Apply a set of transactions, 3. Apply a mining-reward (*optional), 4. And generate a post-state, including -- State root, transaction root, receipt root, -- Information about rejected transactions, -- Optionally: a full or partial post-state dump + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump -## Specification +### Specification The idea is to specify the behaviour of this binary very _strict_, so that other node implementors can build replicas based on their own state-machines, and the -state generators can swap between a `geth`-based implementation and a `parityvm`-based +state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based implementation. -### Command line params +#### Command line params + +Command line params that need to be supported are + +``` + --input.alloc value (default: "alloc.json") + --input.env value (default: "env.json") + --input.txs value (default: "txs.json") + --output.alloc value (default: "alloc.json") + --output.basedir value + --output.body value + --output.result value (default: "result.json") + --state.chainid value (default: 1) + --state.fork value (default: "GrayGlacier") + --state.reward value (default: 0) + --trace.memory (default: false) + --trace.nomemory (default: true) + --trace.noreturndata (default: true) + --trace.nostack (default: false) + --trace.returndata (default: false) +``` +#### Objects + +The transition tool uses JSON objects to read and write data related to the transition operation. The +following object definitions are required. + +##### `alloc` + +The `alloc` object defines the prestate that transition will begin with. + +```go +// Map of address to account definition. +type Alloc map[common.Address]Account +// Genesis account. Each field is optional. +type Account struct { + Code []byte `json:"code"` + Storage map[common.Hash]common.Hash `json:"storage"` + Balance *big.Int `json:"balance"` + Nonce uint64 `json:"nonce"` + SecretKey []byte `json:"secretKey"` +} +``` + +##### `env` + +The `env` object defines the environmental context in which the transition will +take place. + +```go +type Env struct { + // required + CurrentCoinbase common.Address `json:"currentCoinbase"` + CurrentGasLimit uint64 `json:"currentGasLimit"` + CurrentNumber uint64 `json:"currentNumber"` + CurrentTimestamp uint64 `json:"currentTimestamp"` + Withdrawals []*Withdrawal `json:"withdrawals"` + // optional + CurrentDifficulty *big.Int `json:"currentDifficuly"` + CurrentRandom *big.Int `json:"currentRandom"` + CurrentBaseFee *big.Int `json:"currentBaseFee"` + ParentDifficulty *big.Int `json:"parentDifficulty"` + ParentGasUsed uint64 `json:"parentGasUsed"` + ParentGasLimit uint64 `json:"parentGasLimit"` + ParentTimestamp uint64 `json:"parentTimestamp"` + BlockHashes map[uint64]common.Hash `json:"blockHashes"` + ParentUncleHash common.Hash `json:"parentUncleHash"` + Ommers []Ommer `json:"ommers"` +} +type Ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} +type Withdrawal struct { + Index uint64 `json:"index"` + ValidatorIndex uint64 `json:"validatorIndex"` + Recipient common.Address `json:"recipient"` + Amount *big.Int `json:"amount"` +} +``` + +##### `txs` -Command line params that has to be supported are +The `txs` object is an array of any of the transaction types: `LegacyTx`, +`AccessListTx`, or `DynamicFeeTx`. + +```go +type LegacyTx struct { + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type AccessList []AccessTuple +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} +type AccessListTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type DynamicFeeTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasTipCap *big.Int `json:"maxPriorityFeePerGas"` + GasFeeCap *big.Int `json:"maxFeePerGas"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} ``` - --trace Output full trace logs to files .jsonl - --trace.nomemory Disable full memory dump in traces - --trace.nostack Disable stack output in traces - --trace.noreturndata Disable return data output in traces - --output.basedir value Specifies where output files are placed. Will be created if it does not exist. - --output.alloc alloc Determines where to put the alloc of the post-state. - `stdout` - into the stdout output - `stderr` - into the stderr output - --output.result result Determines where to put the result (stateroot, txroot etc) of the post-state. - `stdout` - into the stdout output - `stderr` - into the stderr output - --output.body value If set, the RLP of the transactions (block body) will be written to this file. - --input.txs stdin stdin or file name of where to find the transactions to apply. If the file prefix is '.rlp', then the data is interpreted as an RLP list of signed transactions.The '.rlp' format is identical to the output.body format. (default: "txs.json") - --state.fork value Name of ruleset to use. - --state.chainid value ChainID to use (default: 1) - --state.reward value Mining reward. Set to -1 to disable (default: 0) +##### `result` + +The `result` object is output after a transition is executed. It includes +information about the post-transition environment. +```go +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *big.Int `json:"currentDifficulty"` + GasUsed uint64 `json:"gasUsed"` + BaseFee *big.Int `json:"currentBaseFee,omitempty"` +} ``` -### Error codes and output +#### Error codes and output All logging should happen against the `stderr`. There are a few (not many) errors that can occur, those are defined below. -#### EVM-based errors (`2` to `9`) +##### EVM-based errors (`2` to `9`) - Other EVM error. Exit code `2` - Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. @@ -58,18 +198,30 @@ There are a few (not many) errors that can occur, those are defined below. is invoked targeting a block which history has not been provided for, the program will exit with code `4`. -#### IO errors (`10`-`20`) +##### IO errors (`10`-`20`) - Invalid input json: the supplied data could not be marshalled. The program will exit with code `10` - IO problems: failure to load or save files, the program will exit with code `11` -## Examples +``` +# This should exit with 3 +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +exitcode:3 OK +``` +#### Forks ### Basic usage +The chain configuration to be used for a transition is specified via the +`--state.fork` CLI flag. A list of possible values and configurations can be +found in [`tests/init.go`](tests/init.go). + +#### Examples +##### Basic usage + Invoking it with the provided example files ``` -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin ``` Two resulting files: @@ -94,7 +246,7 @@ Two resulting files: { "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", - "receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "receipts": [ @@ -116,78 +268,82 @@ Two resulting files: "index": 1, "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" } - ] + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x5208" } ``` We can make them spit out the data to e.g. `stdout` like this: ``` -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin ``` Output: ```json { - "alloc": { - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "balance": "0xfeed1a9d", - "nonce": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "nonce": "0xac" + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xa410" - } - }, - "result": { - "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", - "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", - "receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", + "result": { + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "rejected": [ - { - "index": 1, - "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" - } - ] - } + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + { + "index": 1, + "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x5208" + } } ``` -## About Ommers +#### About Ommers Mining rewards and ommer rewards might need to be added. This is how those are applied: - `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. - For each ommer (mined by `0xbb`), with blocknumber `N-delta` - - (where `delta` is the difference between the current block and the ommer) - - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` - - The account `0xaa` (block miner) is awarded `block_reward / 32` + - (where `delta` is the difference between the current block and the ommer) + - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` + - The account `0xaa` (block miner) is awarded `block_reward / 32` -To make `state_t8n` apply these, the following inputs are required: +To make `t8n` apply these, the following inputs are required: -- `state.reward` +- `--state.reward` - For ethash, it is `5000000000000000000` `wei`, - If this is not defined, mining rewards are not applied, - A value of `0` is valid, and causes accounts to be 'touched'. -- For each ommer, the tool needs to be given an `address` and a `delta`. This - is done via the `env`. +- For each ommer, the tool needs to be given an `addres\` and a `delta`. This + is done via the `ommers` field in `env`. Note: the tool does not verify that e.g. the normal uncle rules apply, and allows e.g two uncles at the same height, or the uncle-distance. This means that @@ -208,42 +364,38 @@ Example: ] } ``` -When applying this, using a reward of `0x80` +When applying this, using a reward of `0x08` Output: ```json { - "alloc": { - "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { - "balance": "0x88" - }, - "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { - "balance": "0x70" - }, - "0xcccccccccccccccccccccccccccccccccccccccc": { - "balance": "0x60" + "alloc": { + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { + "balance": "0x88" + }, + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { + "balance": "0x70" + }, + "0xcccccccccccccccccccccccccccccccccccccccc": { + "balance": "0x60" + } } - } } ``` -### Future EIPS +#### Future EIPS It is also possible to experiment with future eips that are not yet defined in a hard fork. -Example, putting EIP-1344 into Frontier: +Example, putting EIP-1344 into Frontier: ``` ./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json ``` -### Block history +#### Block history The `BLOCKHASH` opcode requires blockhashes to be provided by the caller, inside the `env`. If a required blockhash is not provided, the exit code should be `4`: -Example where blockhashes are provided: +Example where blockhashes are provided: ``` -./evm --verbosity=1 t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace -INFO [07-27|11:53:40.960] Trie dumping started root=b7341d..857ea1 -INFO [07-27|11:53:40.960] Trie dumping complete accounts=3 elapsed="103.298µs" -INFO [07-27|11:53:40.960] Wrote file file=alloc.json -INFO [07-27|11:53:40.960] Wrote file file=result.json +./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin ``` @@ -251,44 +403,34 @@ INFO [07-27|11:53:40.960] Wrote file file=result.j cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 ``` ``` -{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnData":"0x","depth":1,"refund":0,"opName":"PUSH1","error":""} -{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnData":"0x","depth":1,"refund":0,"opName":"BLOCKHASH","error":""} -{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnData":"0x","depth":1,"refund":0,"opName":"STOP","error":""} -{"output":"","gasUsed":"0x17","time":156276} +{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"BLOCKHASH"} +{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"depth":1,"refund":0,"opName":"STOP"} +{"output":"","gasUsed":"0x17"} ``` In this example, the caller has not provided the required blockhash: ``` -./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace +./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin ERROR(4): getHash(3) invoked, blockhash for that block not provided ``` Error code: 4 -### Chaining +#### Chaining Another thing that can be done, is to chain invocations: ``` -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json -INFO [07-27|11:53:41.049] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" -INFO [07-27|11:53:41.050] Trie dumping started root=84208a..ae4e13 -INFO [07-27|11:53:41.050] Trie dumping complete accounts=3 elapsed="59.412µs" -INFO [07-27|11:53:41.050] Wrote file file=result.json -INFO [07-27|11:53:41.051] rejected tx index=0 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" -INFO [07-27|11:53:41.051] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" -INFO [07-27|11:53:41.052] Trie dumping started root=84208a..ae4e13 -INFO [07-27|11:53:41.052] Trie dumping complete accounts=3 elapsed="45.734µs" -INFO [07-27|11:53:41.052] Wrote file file=alloc.json -INFO [07-27|11:53:41.052] Wrote file file=result.json +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin ``` -What happened here, is that we first applied two identical transactions, so the second one was rejected. +What happened here, is that we first applied two identical transactions, so the second one was rejected. Then, taking the poststate alloc as the input for the next state, we tried again to include the same two transactions: this time, both failed due to too low nonce. In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the actual blocknumber (exposed to the EVM) would not increase. -### Transactions in RLP form +#### Transactions in RLP form It is possible to provide already-signed transactions as input to, using an `input.txs` which ends with the `rlp` suffix. The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible @@ -297,12 +439,11 @@ to use the evm to go from `json` input to `rlp` input. The following command takes **json** the transactions in `./testdata/13/txs.json` and signs them. After execution, they are output to `signed_txs.rlp`.: ``` ./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp -INFO [07-27|11:53:41.124] Trie dumping started root=e4b924..6aef61 -INFO [07-27|11:53:41.124] Trie dumping complete accounts=3 elapsed="94.284µs" -INFO [07-27|11:53:41.125] Wrote file file=alloc.json -INFO [07-27|11:53:41.125] Wrote file file=alloc_jsontx.json -INFO [07-27|11:53:41.125] Wrote file file=signed_txs.rlp - +INFO [12-07|04:30:12.380] Trie dumping started root=e4b924..6aef61 +INFO [12-07|04:30:12.380] Trie dumping complete accounts=3 elapsed="85.765µs" +INFO [12-07|04:30:12.380] Wrote file file=alloc.json +INFO [12-07|04:30:12.380] Wrote file file=alloc_jsontx.json +INFO [12-07|04:30:12.380] Wrote file file=signed_txs.rlp ``` The `output.body` is the rlp-list of transactions, encoded in hex and placed in a string a'la `json` encoding rules: @@ -311,7 +452,7 @@ cat signed_txs.rlp "0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" ``` -We can use `rlpdump` to check what the contents are: +We can use `rlpdump` to check what the contents are: ``` rlpdump -hex $(cat signed_txs.rlp | jq -r ) [ @@ -319,17 +460,15 @@ rlpdump -hex $(cat signed_txs.rlp | jq -r ) 02f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9, ] ``` -Now, we can now use those (or any other already signed transactions), as input, like so: +Now, we can now use those (or any other already signed transactions), as input, like so: ``` ./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json -INFO [07-27|11:53:41.253] Trie dumping started root=e4b924..6aef61 -INFO [07-27|11:53:41.253] Trie dumping complete accounts=3 elapsed="128.445µs" -INFO [07-27|11:53:41.253] Wrote file file=alloc.json -INFO [07-27|11:53:41.255] Wrote file file=alloc_rlptx.json - +INFO [12-07|04:30:12.425] Trie dumping started root=e4b924..6aef61 +INFO [12-07|04:30:12.425] Trie dumping complete accounts=3 elapsed="70.684µs" +INFO [12-07|04:30:12.425] Wrote file file=alloc.json +INFO [12-07|04:30:12.425] Wrote file file=alloc_rlptx.json ``` - -You might have noticed that the results from these two invocations were stored in two separate files. +You might have noticed that the results from these two invocations were stored in two separate files. And we can now finally check that they match. ``` cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index 031def0211b1..b7a0d9c2c3c7 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -230,7 +230,7 @@ func TestT8n(t *testing.T) { { // Test post-merge transition base: "./testdata/24", input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Merged", "", + "alloc.json", "txs.json", "env.json", "Merge", "", }, output: t8nOutput{alloc: true, result: true}, expOut: "exp.json", @@ -238,7 +238,7 @@ func TestT8n(t *testing.T) { { // Test post-merge transition where input is missing random base: "./testdata/24", input: t8nInput{ - "alloc.json", "txs.json", "env-missingrandom.json", "Merged", "", + "alloc.json", "txs.json", "env-missingrandom.json", "Merge", "", }, output: t8nOutput{alloc: false, result: false}, expExitCode: 3, @@ -246,7 +246,7 @@ func TestT8n(t *testing.T) { { // Test base fee calculation base: "./testdata/25", input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Merged", "", + "alloc.json", "txs.json", "env.json", "Merge", "", }, output: t8nOutput{alloc: true, result: true}, expOut: "exp.json", diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh index 250238d1694a..8d7fd799732c 100644 --- a/cmd/evm/transition-test.sh +++ b/cmd/evm/transition-test.sh @@ -20,10 +20,24 @@ function tick(){ echo "$ticks" } -cat << EOF -## EVM state transition tool +function code(){ + echo "$ticks$1" +} + +cat << "EOF" +# EVM tool + +The EVM tool provides a few useful subcommands to facilitate testing at the EVM +layer. + +* transition tool (`t8n`) : a stateless state transition utility +* transaction tool (`t9n`) : a transaction validation utility +* block builder tool (`b11r`): a block assembler utility -The \`evm t8n\` tool is a stateless state transition utility. It is a utility +## State transition tool (`t8n`) + + +The `evm t8n` tool is a stateless state transition utility. It is a utility which can 1. Take a prestate, including @@ -37,55 +51,200 @@ which can - Information about rejected transactions, - Optionally: a full or partial post-state dump -## Specification +### Specification The idea is to specify the behaviour of this binary very _strict_, so that other node implementors can build replicas based on their own state-machines, and the state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based implementation. -### Command line params +#### Command line params -Command line params that has to be supported are -$(tick) +Command line params that need to be supported are -` ./evm t8n -h | grep "trace\|output\|state\."` - -$(tick) +``` +EOF +./evm t8n -h | grep "\-\-trace\.\|\-\-output\.\|\-\-state\.\|\-\-input" +cat << "EOF" +``` +#### Objects + +The transition tool uses JSON objects to read and write data related to the transition operation. The +following object definitions are required. + +##### `alloc` + +The `alloc` object defines the prestate that transition will begin with. + +```go +// Map of address to account definition. +type Alloc map[common.Address]Account +// Genesis account. Each field is optional. +type Account struct { + Code []byte `json:"code"` + Storage map[common.Hash]common.Hash `json:"storage"` + Balance *big.Int `json:"balance"` + Nonce uint64 `json:"nonce"` + SecretKey []byte `json:"secretKey"` +} +``` + +##### `env` + +The `env` object defines the environmental context in which the transition will +take place. + +```go +type Env struct { + // required + CurrentCoinbase common.Address `json:"currentCoinbase"` + CurrentGasLimit uint64 `json:"currentGasLimit"` + CurrentNumber uint64 `json:"currentNumber"` + CurrentTimestamp uint64 `json:"currentTimestamp"` + Withdrawals []*Withdrawal `json:"withdrawals"` + // optional + CurrentDifficulty *big.Int `json:"currentDifficuly"` + CurrentRandom *big.Int `json:"currentRandom"` + CurrentBaseFee *big.Int `json:"currentBaseFee"` + ParentDifficulty *big.Int `json:"parentDifficulty"` + ParentGasUsed uint64 `json:"parentGasUsed"` + ParentGasLimit uint64 `json:"parentGasLimit"` + ParentTimestamp uint64 `json:"parentTimestamp"` + BlockHashes map[uint64]common.Hash `json:"blockHashes"` + ParentUncleHash common.Hash `json:"parentUncleHash"` + Ommers []Ommer `json:"ommers"` +} +type Ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} +type Withdrawal struct { + Index uint64 `json:"index"` + ValidatorIndex uint64 `json:"validatorIndex"` + Recipient common.Address `json:"recipient"` + Amount *big.Int `json:"amount"` +} +``` + +##### `txs` + +The `txs` object is an array of any of the transaction types: `LegacyTx`, +`AccessListTx`, or `DynamicFeeTx`. + +```go +type LegacyTx struct { + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type AccessList []AccessTuple +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} +type AccessListTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type DynamicFeeTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasTipCap *big.Int `json:"maxPriorityFeePerGas"` + GasFeeCap *big.Int `json:"maxFeePerGas"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +``` + +##### `result` + +The `result` object is output after a transition is executed. It includes +information about the post-transition environment. + +```go +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *big.Int `json:"currentDifficulty"` + GasUsed uint64 `json:"gasUsed"` + BaseFee *big.Int `json:"currentBaseFee,omitempty"` +} +``` -### Error codes and output +#### Error codes and output -All logging should happen against the \`stderr\`. +All logging should happen against the `stderr`. There are a few (not many) errors that can occur, those are defined below. -#### EVM-based errors (\`2\` to \`9\`) +##### EVM-based errors (`2` to `9`) -- Other EVM error. Exit code \`2\` -- Failed configuration: when a non-supported or invalid fork was specified. Exit code \`3\`. -- Block history is not supplied, but needed for a \`BLOCKHASH\` operation. If \`BLOCKHASH\` +- Other EVM error. Exit code `2` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. +- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` is invoked targeting a block which history has not been provided for, the program will - exit with code \`4\`. + exit with code `4`. -#### IO errors (\`10\`-\`20\`) +##### IO errors (`10`-`20`) - Invalid input json: the supplied data could not be marshalled. - The program will exit with code \`10\` -- IO problems: failure to load or save files, the program will exit with code \`11\` - -EOF + The program will exit with code `10` +- IO problems: failure to load or save files, the program will exit with code `11` +``` # This should exit with 3 ./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null -if [ $? != 3 ]; then - echo "Failed, exitcode should be 3" +EOF +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +exitcode=$? +if [ $exitcode != 3 ]; then + echo "Failed, exitcode should be 3,was $exitcode" +else + echo "exitcode:$exitcode OK" fi -cat << EOF -## Examples +cat << "EOF" +``` +#### Forks ### Basic usage +The chain configuration to be used for a transition is specified via the +`--state.fork` CLI flag. A list of possible values and configurations can be +found in [`tests/init.go`](tests/init.go). + +#### Examples +##### Basic usage + Invoking it with the provided example files EOF -cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json" +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin" tick;echo "$cmd"; tick $cmd 2>/dev/null echo "Two resulting files:" @@ -95,7 +254,7 @@ showjson result.json echo "" echo "We can make them spit out the data to e.g. \`stdout\` like this:" -cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout" +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin" tick;echo "$cmd"; tick output=`$cmd 2>/dev/null` echo "Output:" @@ -103,26 +262,26 @@ echo "${ticks}json" echo "$output" echo "$ticks" -cat << EOF +cat << "EOF" -## About Ommers +#### About Ommers Mining rewards and ommer rewards might need to be added. This is how those are applied: -- \`block_reward\` is the block mining reward for the miner (\`0xaa\`), of a block at height \`N\`. -- For each ommer (mined by \`0xbb\`), with blocknumber \`N-delta\` - - (where \`delta\` is the difference between the current block and the ommer) - - The account \`0xbb\` (ommer miner) is awarded \`(8-delta)/ 8 * block_reward\` - - The account \`0xaa\` (block miner) is awarded \`block_reward / 32\` +- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. +- For each ommer (mined by `0xbb`), with blocknumber `N-delta` + - (where `delta` is the difference between the current block and the ommer) + - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` + - The account `0xaa` (block miner) is awarded `block_reward / 32` -To make \`state_t8n\` apply these, the following inputs are required: +To make `t8n` apply these, the following inputs are required: -- \`state.reward\` - - For ethash, it is \`5000000000000000000\` \`wei\`, +- `--state.reward` + - For ethash, it is `5000000000000000000` `wei`, - If this is not defined, mining rewards are not applied, - - A value of \`0\` is valid, and causes accounts to be 'touched'. -- For each ommer, the tool needs to be given an \`address\` and a \`delta\`. This - is done via the \`env\`. + - A value of `0` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an `addres\` and a `delta`. This + is done via the `ommers` field in `env`. Note: the tool does not verify that e.g. the normal uncle rules apply, and allows e.g two uncles at the same height, or the uncle-distance. This means that @@ -134,14 +293,14 @@ EOF showjson ./testdata/5/env.json echo "When applying this, using a reward of \`0x08\`" -cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80" +cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80 --state.fork=Berlin" output=`$cmd 2>/dev/null` echo "Output:" echo "${ticks}json" echo "$output" echo "$ticks" -echo "### Future EIPS" +echo "#### Future EIPS" echo "" echo "It is also possible to experiment with future eips that are not yet defined in a hard fork." echo "Example, putting EIP-1344 into Frontier: " @@ -149,12 +308,12 @@ cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --in tick;echo "$cmd"; tick echo "" -echo "### Block history" +echo "#### Block history" echo "" echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." echo "If a required blockhash is not provided, the exit code should be \`4\`:" echo "Example where blockhashes are provided: " -demo "./evm --verbosity=1 t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace" +demo "./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin" cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2" tick && echo $cmd && tick echo "$ticks" @@ -163,18 +322,18 @@ echo "$ticks" echo "" echo "In this example, the caller has not provided the required blockhash:" -cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace" -tick && echo $cmd && $cmd +cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin" +tick && echo $cmd && $cmd 2>&1 errc=$? tick echo "Error code: $errc" echo "" -echo "### Chaining" +echo "#### Chaining" echo "" echo "Another thing that can be done, is to chain invocations:" -cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout" -cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json" +cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout" +cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin" echo "$ticks" echo "$cmd1 | $cmd2" output=$($cmd1 | $cmd2 ) @@ -188,14 +347,19 @@ echo "In order to meaningfully chain invocations, one would need to provide mean echo "actual blocknumber (exposed to the EVM) would not increase." echo "" -echo "### Transactions in RLP form" +echo "#### Transactions in RLP form" echo "" echo "It is possible to provide already-signed transactions as input to, using an \`input.txs\` which ends with the \`rlp\` suffix." echo "The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible" echo "to use the evm to go from \`json\` input to \`rlp\` input." echo "" echo "The following command takes **json** the transactions in \`./testdata/13/txs.json\` and signs them. After execution, they are output to \`signed_txs.rlp\`.:" -demo "./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp" +cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp" +echo "$ticks" +echo $cmd +$cmd 2>&1 +echo "$ticks" +echo "" echo "The \`output.body\` is the rlp-list of transactions, encoded in hex and placed in a string a'la \`json\` encoding rules:" demo "cat signed_txs.rlp" echo "We can use \`rlpdump\` to check what the contents are: " @@ -204,8 +368,11 @@ echo "rlpdump -hex \$(cat signed_txs.rlp | jq -r )" rlpdump -hex $(cat signed_txs.rlp | jq -r ) echo "$ticks" echo "Now, we can now use those (or any other already signed transactions), as input, like so: " -demo "./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json" - +cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json" +echo "$ticks" +echo $cmd +$cmd 2>&1 +echo "$ticks" echo "You might have noticed that the results from these two invocations were stored in two separate files. " echo "And we can now finally check that they match." echo "$ticks" diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 6d108856e6d6..274b9cf3603e 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -95,44 +95,71 @@ func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *ty return beacon.verifyHeader(chain, header, parent) } -// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers -// concurrently. The method returns a quit channel to abort the operations and -// a results channel to retrieve the async verifications. -// VerifyHeaders expect the headers to be ordered and continuous. -func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { - if !beacon.IsPoSHeader(headers[len(headers)-1]) { - return beacon.ethone.VerifyHeaders(chain, headers, seals) +// errOut constructs an error channel with prefilled errors inside. +func errOut(n int, err error) chan error { + errs := make(chan error, n) + for i := 0; i < n; i++ { + errs <- err + } + return errs +} + +// splitHeaders splits the provided header batch into two parts according to +// the configured ttd. It requires the parent of header batch along with its +// td are stored correctly in chain. If ttd is not configured yet, all headers +// will be treated legacy PoW headers. +// Note, this function will not verify the header validity but just split them. +func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) ([]*types.Header, []*types.Header, error) { + // TTD is not defined yet, all headers should be in legacy format. + ttd := chain.Config().TerminalTotalDifficulty + if ttd == nil { + return headers, nil, nil + } + ptd := chain.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { + return nil, nil, consensus.ErrUnknownAncestor + } + // The entire header batch already crosses the transition. + if ptd.Cmp(ttd) >= 0 { + return nil, headers, nil } var ( - preHeaders []*types.Header + preHeaders = headers postHeaders []*types.Header - preSeals []bool + td = new(big.Int).Set(ptd) + tdPassed bool ) - for index, header := range headers { - if beacon.IsPoSHeader(header) { - preHeaders = headers[:index] - postHeaders = headers[index:] - preSeals = seals[:index] + for i, header := range headers { + if tdPassed { + preHeaders = headers[:i] + postHeaders = headers[i:] break } + td = td.Add(td, header.Difficulty) + if td.Cmp(ttd) >= 0 { + // This is the last PoW header, it still belongs to + // the preHeaders, so we cannot split+break yet. + tdPassed = true + } } + return preHeaders, postHeaders, nil +} +// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers +// concurrently. The method returns a quit channel to abort the operations and +// a results channel to retrieve the async verifications. +// VerifyHeaders expect the headers to be ordered and continuous. +func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { + preHeaders, postHeaders, err := beacon.splitHeaders(chain, headers) + if err != nil { + return make(chan struct{}), errOut(len(headers), err) + } + if len(postHeaders) == 0 { + return beacon.ethone.VerifyHeaders(chain, headers, seals) + } if len(preHeaders) == 0 { - // All the headers are pos headers. Verify that the parent block reached total terminal difficulty. - if reached, err := IsTTDReached(chain, headers[0].ParentHash, headers[0].Number.Uint64()-1); !reached { - // TTD not reached for the first block, mark subsequent with invalid terminal block - if err == nil { - err = consensus.ErrInvalidTerminalBlock - } - results := make(chan error, len(headers)) - for i := 0; i < len(headers); i++ { - results <- err - } - return make(chan struct{}), results - } return beacon.verifyHeaders(chain, headers, nil) } - // The transition point exists in the middle, separate the headers // into two batches and apply different verification rules for them. var ( @@ -144,16 +171,9 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ old, new, out = 0, len(preHeaders), 0 errors = make([]error, len(headers)) done = make([]bool, len(headers)) - oldDone, oldResult = beacon.ethone.VerifyHeaders(chain, preHeaders, preSeals) + oldDone, oldResult = beacon.ethone.VerifyHeaders(chain, preHeaders, seals[:len(preHeaders)]) newDone, newResult = beacon.verifyHeaders(chain, postHeaders, preHeaders[len(preHeaders)-1]) ) - // Verify that pre-merge headers don't overflow the TTD - if index, err := verifyTerminalPoWBlock(chain, preHeaders); err != nil { - // Mark all subsequent pow headers with the error. - for i := index; i < len(preHeaders); i++ { - errors[i], done[i] = err, true - } - } // Collect the results for { for ; done[out]; out++ { @@ -181,33 +201,6 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ return abort, results } -// verifyTerminalPoWBlock verifies that the preHeaders conform to the specification -// wrt. their total difficulty. -// It expects: -// - preHeaders to be at least 1 element -// - the parent of the header element to be stored in the chain correctly -// - the preHeaders to have a set difficulty -// - the last element to be the terminal block -func verifyTerminalPoWBlock(chain consensus.ChainHeaderReader, preHeaders []*types.Header) (int, error) { - td := chain.GetTd(preHeaders[0].ParentHash, preHeaders[0].Number.Uint64()-1) - if td == nil { - return 0, consensus.ErrUnknownAncestor - } - td = new(big.Int).Set(td) - // Check that all blocks before the last one are below the TTD - for i, head := range preHeaders { - if td.Cmp(chain.Config().TerminalTotalDifficulty) >= 0 { - return i, consensus.ErrInvalidTerminalBlock - } - td.Add(td, head.Difficulty) - } - // Check that the last block is the terminal block - if td.Cmp(chain.Config().TerminalTotalDifficulty) < 0 { - return len(preHeaders) - 1, consensus.ErrInvalidTerminalBlock - } - return 0, nil -} - // VerifyUncles verifies that the given block's uncles conform to the consensus // rules of the Ethereum consensus engine. func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { @@ -419,11 +412,11 @@ func (beacon *Beacon) SetThreads(threads int) { // IsTTDReached checks if the TotalTerminalDifficulty has been surpassed on the `parentHash` block. // It depends on the parentHash already being stored in the database. // If the parentHash is not stored in the database a UnknownAncestor error is returned. -func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, number uint64) (bool, error) { +func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, parentNumber uint64) (bool, error) { if chain.Config().TerminalTotalDifficulty == nil { return false, nil } - td := chain.GetTd(parentHash, number) + td := chain.GetTd(parentHash, parentNumber) if td == nil { return false, consensus.ErrUnknownAncestor } diff --git a/consensus/beacon/consensus_test.go b/consensus/beacon/consensus_test.go deleted file mode 100644 index 09c0b27c4256..000000000000 --- a/consensus/beacon/consensus_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package beacon - -import ( - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" -) - -type mockChain struct { - config *params.ChainConfig - tds map[uint64]*big.Int -} - -func newMockChain() *mockChain { - return &mockChain{ - config: new(params.ChainConfig), - tds: make(map[uint64]*big.Int), - } -} - -func (m *mockChain) Config() *params.ChainConfig { - return m.config -} - -func (m *mockChain) CurrentHeader() *types.Header { panic("not implemented") } - -func (m *mockChain) GetHeader(hash common.Hash, number uint64) *types.Header { - panic("not implemented") -} - -func (m *mockChain) GetHeaderByNumber(number uint64) *types.Header { panic("not implemented") } - -func (m *mockChain) GetHeaderByHash(hash common.Hash) *types.Header { panic("not implemented") } - -func (m *mockChain) GetTd(hash common.Hash, number uint64) *big.Int { - num, ok := m.tds[number] - if ok { - return new(big.Int).Set(num) - } - return nil -} - -func TestVerifyTerminalBlock(t *testing.T) { - chain := newMockChain() - chain.tds[0] = big.NewInt(10) - chain.config.TerminalTotalDifficulty = big.NewInt(50) - - tests := []struct { - preHeaders []*types.Header - ttd *big.Int - err error - index int - }{ - // valid ttd - { - preHeaders: []*types.Header{ - {Number: big.NewInt(1), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(2), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(3), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(4), Difficulty: big.NewInt(10)}, - }, - ttd: big.NewInt(50), - }, - // last block doesn't reach ttd - { - preHeaders: []*types.Header{ - {Number: big.NewInt(1), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(2), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(3), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(4), Difficulty: big.NewInt(9)}, - }, - ttd: big.NewInt(50), - err: consensus.ErrInvalidTerminalBlock, - index: 3, - }, - // two blocks reach ttd - { - preHeaders: []*types.Header{ - {Number: big.NewInt(1), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(2), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(3), Difficulty: big.NewInt(20)}, - {Number: big.NewInt(4), Difficulty: big.NewInt(10)}, - }, - ttd: big.NewInt(50), - err: consensus.ErrInvalidTerminalBlock, - index: 3, - }, - // three blocks reach ttd - { - preHeaders: []*types.Header{ - {Number: big.NewInt(1), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(2), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(3), Difficulty: big.NewInt(20)}, - {Number: big.NewInt(4), Difficulty: big.NewInt(10)}, - {Number: big.NewInt(4), Difficulty: big.NewInt(10)}, - }, - ttd: big.NewInt(50), - err: consensus.ErrInvalidTerminalBlock, - index: 3, - }, - // parent reached ttd - { - preHeaders: []*types.Header{ - {Number: big.NewInt(1), Difficulty: big.NewInt(10)}, - }, - ttd: big.NewInt(9), - err: consensus.ErrInvalidTerminalBlock, - index: 0, - }, - // unknown parent - { - preHeaders: []*types.Header{ - {Number: big.NewInt(4), Difficulty: big.NewInt(10)}, - }, - ttd: big.NewInt(9), - err: consensus.ErrUnknownAncestor, - index: 0, - }, - } - - for i, test := range tests { - fmt.Printf("Test: %v\n", i) - chain.config.TerminalTotalDifficulty = test.ttd - index, err := verifyTerminalPoWBlock(chain, test.preHeaders) - if err != test.err { - t.Fatalf("Invalid error encountered, expected %v got %v", test.err, err) - } - if index != test.index { - t.Fatalf("Invalid index, expected %v got %v", test.index, index) - } - } -} diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 3bdb20e7e1e7..3e951d56a23f 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -17,7 +17,6 @@ package core import ( - "encoding/json" "math/big" "runtime" "testing" @@ -135,31 +134,29 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { config := *params.TestChainConfig gspec = &Genesis{Config: &config} engine = beacon.New(ethash.NewFaker()) - - td := 0 + td := int(params.GenesisDifficulty.Uint64()) genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil) - for _, block := range preBlocks { + for _, block := range blocks { // calculate td td += int(block.Difficulty().Uint64()) } preBlocks = blocks gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td)) - postBlocks, _ = GenerateChain(gspec.Config, preBlocks[len(preBlocks)-1], engine, genDb, 8, nil) + t.Logf("Set ttd to %v\n", gspec.Config.TerminalTotalDifficulty) + postBlocks, _ = GenerateChain(gspec.Config, preBlocks[len(preBlocks)-1], engine, genDb, 8, func(i int, gen *BlockGen) { + gen.SetPoS() + }) } // Assemble header batch preHeaders := make([]*types.Header, len(preBlocks)) for i, block := range preBlocks { preHeaders[i] = block.Header() - - blob, _ := json.Marshal(block.Header()) - t.Logf("Log header before the merging %d: %v", block.NumberU64(), string(blob)) + t.Logf("Pre-merge header: %d", block.NumberU64()) } postHeaders := make([]*types.Header, len(postBlocks)) for i, block := range postBlocks { postHeaders[i] = block.Header() - - blob, _ := json.Marshal(block.Header()) - t.Logf("Log header after the merging %d: %v", block.NumberU64(), string(blob)) + t.Logf("Post-merge header: %d", block.NumberU64()) } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) @@ -172,15 +169,15 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { select { case result := <-results: if result != nil { - t.Errorf("test %d: verification failed %v", i, result) + t.Errorf("pre-block %d: verification failed %v", i, result) } case <-time.After(time.Second): - t.Fatalf("test %d: verification timeout", i) + t.Fatalf("pre-block %d: verification timeout", i) } // Make sure no more data is returned select { case result := <-results: - t.Fatalf("test %d: unexpected result returned: %v", i, result) + t.Fatalf("pre-block %d: unexpected result returned: %v", i, result) case <-time.After(25 * time.Millisecond): } chain.InsertChain(preBlocks[i : i+1]) @@ -197,7 +194,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { select { case result := <-results: if result != nil { - t.Errorf("test %d: verification failed %v", i, result) + t.Errorf("post-block %d: verification failed %v", i, result) } case <-time.After(time.Second): t.Fatalf("test %d: verification timeout", i) @@ -205,7 +202,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { // Make sure no more data is returned select { case result := <-results: - t.Fatalf("test %d: unexpected result returned: %v", i, result) + t.Fatalf("post-block %d: unexpected result returned: %v", i, result) case <-time.After(25 * time.Millisecond): } chain.InsertBlockWithoutSetHead(postBlocks[i]) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index faa4b383feb4..d803617e2f75 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1959,7 +1959,8 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon Alloc: GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - signer = types.LatestSigner(gspec.Config) + signer = types.LatestSigner(gspec.Config) + mergeBlock = math.MaxInt32 ) // Generate and import the canonical chain chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) @@ -1970,6 +1971,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon // Activate the transition since genesis if required if mergePoint == 0 { + mergeBlock = 0 merger.ReachTTD() merger.FinalizePoS() @@ -1982,6 +1984,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon t.Fatalf("failed to create tx: %v", err) } gen.AddTx(tx) + if int(gen.header.Number.Uint64()) >= mergeBlock { + gen.SetPoS() + } nonce++ }) if n, err := chain.InsertChain(blocks); err != nil { @@ -2006,7 +2011,10 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon merger.ReachTTD() merger.FinalizePoS() // Set the terminal total difficulty in the config - gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(len(blocks))) + ttd := big.NewInt(int64(len(blocks))) + ttd.Mul(ttd, params.GenesisDifficulty) + gspec.Config.TerminalTotalDifficulty = ttd + mergeBlock = len(blocks) } // Generate the sidechain @@ -2018,6 +2026,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon parent := blocks[parentIndex] fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) + if int(b.header.Number.Uint64()) >= mergeBlock { + b.SetPoS() + } }) // Prepend the parent(s) var sidechain []*types.Block @@ -2226,25 +2237,45 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i BaseFee: big.NewInt(params.InitialBaseFee), Config: &chainConfig, } - engine = beacon.New(ethash.NewFaker()) + engine = beacon.New(ethash.NewFaker()) + mergeBlock = uint64(math.MaxUint64) ) // Apply merging since genesis if mergeHeight == 0 { genesis.Config.TerminalTotalDifficulty = big.NewInt(0) + mergeBlock = uint64(0) } - genDb, blocks, receipts := GenerateChainWithGenesis(genesis, engine, 32, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + + genDb, blocks, receipts := GenerateChainWithGenesis(genesis, engine, 32, + func(i int, b *BlockGen) { + if b.header.Number.Uint64() >= mergeBlock { + b.SetPoS() + } + b.SetCoinbase(common.Address{1}) + }) // Apply merging after the first segment if mergeHeight == 1 { - genesis.Config.TerminalTotalDifficulty = big.NewInt(int64(len(blocks))) + // TTD is genesis diff + blocks + ttd := big.NewInt(1 + int64(len(blocks))) + ttd.Mul(ttd, params.GenesisDifficulty) + genesis.Config.TerminalTotalDifficulty = ttd + mergeBlock = uint64(len(blocks)) } // Longer chain and shorter chain - blocks2, receipts2 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 65, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + blocks2, receipts2 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 65, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + if b.header.Number.Uint64() >= mergeBlock { + b.SetPoS() + } + }) blocks3, receipts3 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) b.OffsetTime(-9) // Time shifted, difficulty shouldn't be changed + if b.header.Number.Uint64() >= mergeBlock { + b.SetPoS() + } }) - // Import the shared chain and the original canonical one chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) if err != nil { @@ -2268,7 +2299,10 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i for _, block := range blocks { headers = append(headers, block.Header()) } - _, err := chain.InsertHeaderChain(headers, 1) + i, err := chain.InsertHeaderChain(headers, 1) + if err != nil { + return fmt.Errorf("index %d, number %d: %w", i, headers[i].Number, err) + } return err } asserter = func(t *testing.T, block *types.Block) { @@ -2282,9 +2316,9 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i for _, block := range blocks { headers = append(headers, block.Header()) } - _, err := chain.InsertHeaderChain(headers, 1) + i, err := chain.InsertHeaderChain(headers, 1) if err != nil { - return err + return fmt.Errorf("index %d: %w", i, err) } _, err = chain.InsertReceiptChain(blocks, receipts, 0) return err @@ -2296,8 +2330,11 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i } } else { inserter = func(blocks []*types.Block, receipts []types.Receipts) error { - _, err := chain.InsertChain(blocks) - return err + i, err := chain.InsertChain(blocks) + if err != nil { + return fmt.Errorf("index %d: %w", i, err) + } + return nil } asserter = func(t *testing.T, block *types.Block) { if chain.CurrentBlock().Hash() != block.Hash() { diff --git a/core/chain_makers.go b/core/chain_makers.go index 52dd6e2e47be..cbfe5c3ece16 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -80,6 +80,11 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) { b.header.Difficulty = diff } +// SetPos makes the header a PoS-header (0 difficulty) +func (b *BlockGen) SetPoS() { + b.header.Difficulty = new(big.Int) +} + // addTx adds a transaction to the generated block. If no coinbase has // been set, the block's coinbase is set to the zero address. // diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 7872c7f711f1..00ef2203f832 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -852,11 +852,11 @@ func TestInvalidBloom(t *testing.T) { func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { genesis, preMergeBlocks := generatePreMergeChain(100) - genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() + genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) var ( api = NewConsensusAPI(ethservice) parent = preMergeBlocks[len(preMergeBlocks)-1] diff --git a/les/catalyst/api_test.go b/les/catalyst/api_test.go index 6d0eedeccb77..91d5c9bbb988 100644 --- a/les/catalyst/api_test.go +++ b/les/catalyst/api_test.go @@ -44,7 +44,7 @@ var ( testBalance = big.NewInt(2e18) ) -func generatePreMergeChain(n int) (*core.Genesis, []*types.Header, []*types.Block) { +func generatePreMergeChain(pre, post int) (*core.Genesis, []*types.Header, []*types.Block, []*types.Header, []*types.Block) { config := *params.AllEthashProtocolChanges genesis := &core.Genesis{ Config: &config, @@ -53,21 +53,33 @@ func generatePreMergeChain(n int) (*core.Genesis, []*types.Header, []*types.Bloc Timestamp: 9000, BaseFee: big.NewInt(params.InitialBaseFee), } - _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), n, nil) - totalDifficulty := big.NewInt(0) + // Pre-merge blocks + db, preBLocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), pre, nil) + totalDifficulty := new(big.Int).Set(params.GenesisDifficulty) - var headers []*types.Header - for _, b := range blocks { + var preHeaders []*types.Header + for _, b := range preBLocks { totalDifficulty.Add(totalDifficulty, b.Difficulty()) - headers = append(headers, b.Header()) + preHeaders = append(preHeaders, b.Header()) } config.TerminalTotalDifficulty = totalDifficulty + // Post-merge blocks + postBlocks, _ := core.GenerateChain(genesis.Config, + preBLocks[len(preBLocks)-1], ethash.NewFaker(), db, post, + func(i int, b *core.BlockGen) { + b.SetPoS() + }) - return genesis, headers, blocks + var postHeaders []*types.Header + for _, b := range postBlocks { + postHeaders = append(postHeaders, b.Header()) + } + + return genesis, preHeaders, preBLocks, postHeaders, postBlocks } func TestSetHeadBeforeTotalDifficulty(t *testing.T) { - genesis, headers, blocks := generatePreMergeChain(10) + genesis, headers, blocks, _, _ := generatePreMergeChain(10, 0) n, lesService := startLesService(t, genesis, headers) defer n.Close() @@ -83,21 +95,21 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) { } func TestExecutePayloadV1(t *testing.T) { - genesis, headers, blocks := generatePreMergeChain(10) - n, lesService := startLesService(t, genesis, headers[:9]) + genesis, headers, _, _, postBlocks := generatePreMergeChain(10, 2) + n, lesService := startLesService(t, genesis, headers) lesService.Merger().ReachTTD() defer n.Close() api := NewConsensusAPI(lesService) fcState := beacon.ForkchoiceStateV1{ - HeadBlockHash: blocks[8].Hash(), + HeadBlockHash: postBlocks[0].Hash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { t.Errorf("Failed to update head %v", err) } - block := blocks[9] + block := postBlocks[0] fakeBlock := types.NewBlock(&types.Header{ ParentHash: block.ParentHash(), diff --git a/tests/block_test.go b/tests/block_test.go index 74c7ed819763..9e72f7f9710a 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -46,6 +46,7 @@ func TestBlockchain(t *testing.T) { // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, // using 4.6 TGas bt.skipLoad(`.*randomStatetest94.json.*`) + bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { if err := bt.checkFailure(t, test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 5b200a60727c..df4f08a76412 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -120,6 +121,9 @@ func (t *BlockTest) Run(snapshotter bool) error { } else { engine = ethash.NewShared() } + // Wrap the original engine within the beacon-engine + engine = beacon.New(engine) + cache := &core.CacheConfig{TrieCleanLimit: 0} if snapshotter { cache.SnapshotLimit = 1 diff --git a/tests/init.go b/tests/init.go index ef5ea4bb9a9a..e6faa483a68d 100644 --- a/tests/init.go +++ b/tests/init.go @@ -197,6 +197,24 @@ var Forks = map[string]*params.ChainConfig{ LondonBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0), }, + "ArrowGlacierToMergeAtDiffC0000": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0xC0000), + }, "GrayGlacier": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), @@ -213,7 +231,7 @@ var Forks = map[string]*params.ChainConfig{ ArrowGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0), }, - "Merged": { + "Merge": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), diff --git a/tests/state_test.go b/tests/state_test.go index cb7f76521780..64d01076c956 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -56,14 +56,12 @@ func TestState(t *testing.T) { // Uses 1GB RAM per tested fork st.skipLoad(`^stStaticCall/static_Call1MB`) + // Not yet supported TODO + st.skipLoad(`^stEIP3540/`) + st.skipLoad(`^stEIP3860/`) + // Broken tests: // Expected failures: - // st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Byzantium/0`, "bug in test") - // st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Byzantium/3`, "bug in test") - // st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Constantinople/0`, "bug in test") - // st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Constantinople/3`, "bug in test") - // st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/ConstantinopleFix/0`, "bug in test") - // st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/ConstantinopleFix/3`, "bug in test") // For Istanbul, older tests were moved into LegacyTests for _, dir := range []string{ diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 838e85dca2b7..67605a127339 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -249,10 +249,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.GetHash = vmTestBlockHash context.BaseFee = baseFee context.Random = nil - if config.IsLondon(new(big.Int)) && t.json.Env.Random != nil { - rnd := common.BigToHash(t.json.Env.Random) - context.Random = &rnd + if config.IsLondon(new(big.Int)) { + if t.json.Env.Random != nil { + rnd := common.BigToHash(t.json.Env.Random) + context.Random = &rnd + } context.Difficulty = big.NewInt(0) + } else { + if t.json.Env.Difficulty != nil { + context.Difficulty = new(big.Int).Set(t.json.Env.Difficulty) + } } evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) // Execute the message. diff --git a/tests/testdata b/tests/testdata index a380655e5ffa..24fa31adb30f 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit a380655e5ffab1a5ea0f4d860224bdb19013f06a +Subproject commit 24fa31adb30f71ee700b27decb5204e53a11d9f3