tests: update tests (#26314)

This PR builds on #26299, but also updates the tests to the most recent version, which includes tests regarding TheMerge.

This change adds checks to the beacon consensus engine, making it more strict in validating the pre- and post-headers, and not relying on the caller to have already correctly sanitized the headers/blocks.
pull/26392/head
Martin Holst Swende 2 years ago committed by GitHub
parent 79a478bb61
commit b818e73ef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 397
      cmd/evm/README.md
  2. 6
      cmd/evm/t8n_test.go
  3. 275
      cmd/evm/transition-test.sh
  4. 121
      consensus/beacon/consensus.go
  5. 137
      consensus/beacon/consensus_test.go
  6. 29
      core/block_validator_test.go
  7. 61
      core/blockchain_test.go
  8. 5
      core/chain_makers.go
  9. 2
      eth/catalyst/api_test.go
  10. 36
      les/catalyst/api_test.go
  11. 1
      tests/block_test.go
  12. 4
      tests/block_test_util.go
  13. 20
      tests/init.go
  14. 10
      tests/state_test.go
  15. 12
      tests/state_test_util.go
  16. 2
      tests/testdata

@ -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 The `evm t8n` tool is a stateless state transition utility. It is a utility
which can which can
1. Take a prestate, including 1. Take a prestate, including
- Accounts, - Accounts,
- Block context information, - Block context information,
- Previous blockshashes (*optional) - Previous blockshashes (*optional)
2. Apply a set of transactions, 2. Apply a set of transactions,
3. Apply a mining-reward (*optional), 3. Apply a mining-reward (*optional),
4. And generate a post-state, including 4. And generate a post-state, including
- State root, transaction root, receipt root, - State root, transaction root, receipt root,
- Information about rejected transactions, - Information about rejected transactions,
- Optionally: a full or partial post-state dump - 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 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 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. 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 <txhash>.jsonl ##### `result`
--trace.nomemory Disable full memory dump in traces
--trace.nostack Disable stack output in traces The `result` object is output after a transition is executed. It includes
--trace.noreturndata Disable return data output in traces information about the post-transition environment.
--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)
```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. 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` - Other EVM error. Exit code `2`
- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. - 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 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. - Invalid input json: the supplied data could not be marshalled.
The program will exit with code `10` The program will exit with code `10`
- IO problems: failure to load or save files, the program will exit with code `11` - 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 ### 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 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: Two resulting files:
@ -94,7 +246,7 @@ Two resulting files:
{ {
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13",
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d",
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"receipts": [ "receipts": [
@ -116,78 +268,82 @@ Two resulting files:
"index": 1, "index": 1,
"error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 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: 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: Output:
```json ```json
{ {
"alloc": { "alloc": {
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": {
"balance": "0xfeed1a9d", "balance": "0xfeed1a9d",
"nonce": "0x1" "nonce": "0x1"
}, },
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0x5ffd4878be161d74", "balance": "0x5ffd4878be161d74",
"nonce": "0xac" "nonce": "0xac"
},
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0xa410"
}
}, },
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { "result": {
"balance": "0xa410" "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13",
} "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d",
}, "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
"result": { "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13",
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d",
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"receipts": [
{
"root": "0x",
"status": "0x1",
"cumulativeGasUsed": "0x5208",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"logs": null, "receipts": [
"transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", {
"contractAddress": "0x0000000000000000000000000000000000000000", "root": "0x",
"gasUsed": "0x5208", "status": "0x1",
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "cumulativeGasUsed": "0x5208",
"transactionIndex": "0x0" "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
} "logs": null,
], "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673",
"rejected": [ "contractAddress": "0x0000000000000000000000000000000000000000",
{ "gasUsed": "0x5208",
"index": 1, "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" "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: 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`. - `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` - For each ommer (mined by `0xbb`), with blocknumber `N-delta`
- (where `delta` is the difference between the current block and the ommer) - (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 `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward`
- The account `0xaa` (block miner) is awarded `block_reward / 32` - 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`, - For ethash, it is `5000000000000000000` `wei`,
- If this is not defined, mining rewards are not applied, - If this is not defined, mining rewards are not applied,
- A value of `0` is valid, and causes accounts to be 'touched'. - 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 - For each ommer, the tool needs to be given an `addres\` and a `delta`. This
is done via the `env`. is done via the `ommers` field in `env`.
Note: the tool does not verify that e.g. the normal uncle rules apply, 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 and allows e.g two uncles at the same height, or the uncle-distance. This means that
@ -208,24 +364,24 @@ Example:
] ]
} }
``` ```
When applying this, using a reward of `0x80` When applying this, using a reward of `0x08`
Output: Output:
```json ```json
{ {
"alloc": { "alloc": {
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": {
"balance": "0x88" "balance": "0x88"
}, },
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": {
"balance": "0x70" "balance": "0x70"
}, },
"0xcccccccccccccccccccccccccccccccccccccccc": { "0xcccccccccccccccccccccccccccccccccccccccc": {
"balance": "0x60" "balance": "0x60"
}
} }
}
} }
``` ```
### Future EIPS #### Future EIPS
It is also possible to experiment with future eips that are not yet defined in a hard fork. 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:
@ -233,17 +389,13 @@ 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 ./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`. 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`: 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 ./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin
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
``` ```
@ -251,34 +403,24 @@ INFO [07-27|11:53:40.960] Wrote file file=result.j
cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 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":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnData":"0x","depth":1,"refund":0,"opName":"BLOCKHASH","error":""} {"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","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnData":"0x","depth":1,"refund":0,"opName":"STOP","error":""} {"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x17","time":156276} {"output":"","gasUsed":"0x17"}
``` ```
In this example, the caller has not provided the required blockhash: 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(4): getHash(3) invoked, blockhash for that block not provided
``` ```
Error code: 4 Error code: 4
### Chaining #### Chaining
Another thing that can be done, is to chain invocations: 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 ./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
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
``` ```
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.
@ -288,7 +430,7 @@ 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 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. 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. 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 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`.: 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 ./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 [12-07|04:30:12.380] Trie dumping started root=e4b924..6aef61
INFO [07-27|11:53:41.124] Trie dumping complete accounts=3 elapsed="94.284µs" INFO [12-07|04:30:12.380] Trie dumping complete accounts=3 elapsed="85.765µs"
INFO [07-27|11:53:41.125] Wrote file file=alloc.json INFO [12-07|04:30:12.380] Wrote file file=alloc.json
INFO [07-27|11:53:41.125] Wrote file file=alloc_jsontx.json INFO [12-07|04:30:12.380] 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] 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: The `output.body` is the rlp-list of transactions, encoded in hex and placed in a string a'la `json` encoding rules:
@ -322,13 +463,11 @@ rlpdump -hex $(cat signed_txs.rlp | jq -r )
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 ./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 [12-07|04:30:12.425] Trie dumping started root=e4b924..6aef61
INFO [07-27|11:53:41.253] Trie dumping complete accounts=3 elapsed="128.445µs" INFO [12-07|04:30:12.425] Trie dumping complete accounts=3 elapsed="70.684µs"
INFO [07-27|11:53:41.253] Wrote file file=alloc.json INFO [12-07|04:30:12.425] 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] 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. And we can now finally check that they match.
``` ```

@ -230,7 +230,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition { // Test post-merge transition
base: "./testdata/24", base: "./testdata/24",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merged", "", "alloc.json", "txs.json", "env.json", "Merge", "",
}, },
output: t8nOutput{alloc: true, result: true}, output: t8nOutput{alloc: true, result: true},
expOut: "exp.json", expOut: "exp.json",
@ -238,7 +238,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition where input is missing random { // Test post-merge transition where input is missing random
base: "./testdata/24", base: "./testdata/24",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env-missingrandom.json", "Merged", "", "alloc.json", "txs.json", "env-missingrandom.json", "Merge", "",
}, },
output: t8nOutput{alloc: false, result: false}, output: t8nOutput{alloc: false, result: false},
expExitCode: 3, expExitCode: 3,
@ -246,7 +246,7 @@ func TestT8n(t *testing.T) {
{ // Test base fee calculation { // Test base fee calculation
base: "./testdata/25", base: "./testdata/25",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merged", "", "alloc.json", "txs.json", "env.json", "Merge", "",
}, },
output: t8nOutput{alloc: true, result: true}, output: t8nOutput{alloc: true, result: true},
expOut: "exp.json", expOut: "exp.json",

@ -20,10 +20,24 @@ function tick(){
echo "$ticks" echo "$ticks"
} }
cat << EOF function code(){
## EVM state transition tool 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 which can
1. Take a prestate, including 1. Take a prestate, including
@ -37,55 +51,200 @@ which can
- Information about rejected transactions, - Information about rejected transactions,
- Optionally: a full or partial post-state dump - 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 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 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. implementation.
### Command line params #### Command line params
Command line params that has to be supported are Command line params that need to be supported are
$(tick)
` ./evm t8n -h | grep "trace\|output\|state\."` ```
EOF
$(tick) ./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. 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\` - Other EVM error. Exit code `2`
- Failed configuration: when a non-supported or invalid fork was specified. Exit code \`3\`. - 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\` - 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 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. - Invalid input json: the supplied data could not be marshalled.
The program will exit with code \`10\` The program will exit with code `10`
- IO problems: failure to load or save files, the program will exit with code \`11\` - IO problems: failure to load or save files, the program will exit with code `11`
EOF
```
# This should exit with 3 # 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 ./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 EOF
echo "Failed, exitcode should be 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=$?
if [ $exitcode != 3 ]; then
echo "Failed, exitcode should be 3,was $exitcode"
else
echo "exitcode:$exitcode OK"
fi fi
cat << EOF cat << "EOF"
## Examples ```
#### Forks
### Basic usage ### 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 Invoking it with the provided example files
EOF 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 tick;echo "$cmd"; tick
$cmd 2>/dev/null $cmd 2>/dev/null
echo "Two resulting files:" echo "Two resulting files:"
@ -95,7 +254,7 @@ showjson result.json
echo "" echo ""
echo "We can make them spit out the data to e.g. \`stdout\` like this:" 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 tick;echo "$cmd"; tick
output=`$cmd 2>/dev/null` output=`$cmd 2>/dev/null`
echo "Output:" echo "Output:"
@ -103,26 +262,26 @@ echo "${ticks}json"
echo "$output" echo "$output"
echo "$ticks" 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: 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\`. - `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\` - For each ommer (mined by `0xbb`), with blocknumber `N-delta`
- (where \`delta\` is the difference between the current block and the ommer) - (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 `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward`
- The account \`0xaa\` (block miner) is awarded \`block_reward / 32\` - 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\`, - For ethash, it is `5000000000000000000` `wei`,
- If this is not defined, mining rewards are not applied, - If this is not defined, mining rewards are not applied,
- A value of \`0\` is valid, and causes accounts to be 'touched'. - 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 - For each ommer, the tool needs to be given an `addres\` and a `delta`. This
is done via the \`env\`. is done via the `ommers` field in `env`.
Note: the tool does not verify that e.g. the normal uncle rules apply, 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 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 showjson ./testdata/5/env.json
echo "When applying this, using a reward of \`0x08\`" 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` output=`$cmd 2>/dev/null`
echo "Output:" echo "Output:"
echo "${ticks}json" echo "${ticks}json"
echo "$output" echo "$output"
echo "$ticks" echo "$ticks"
echo "### Future EIPS" echo "#### Future EIPS"
echo "" echo ""
echo "It is also possible to experiment with future eips that are not yet defined in a hard fork." 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: " 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 tick;echo "$cmd"; tick
echo "" echo ""
echo "### Block history" echo "#### Block history"
echo "" echo ""
echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." 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 "If a required blockhash is not provided, the exit code should be \`4\`:"
echo "Example where blockhashes are provided: " 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" cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2"
tick && echo $cmd && tick tick && echo $cmd && tick
echo "$ticks" echo "$ticks"
@ -163,18 +322,18 @@ echo "$ticks"
echo "" echo ""
echo "In this example, the caller has not provided the required blockhash:" 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" 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 tick && echo $cmd && $cmd 2>&1
errc=$? errc=$?
tick tick
echo "Error code: $errc" echo "Error code: $errc"
echo "" echo ""
echo "### Chaining" echo "#### Chaining"
echo "" echo ""
echo "Another thing that can be done, is to chain invocations:" 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" 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" cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin"
echo "$ticks" echo "$ticks"
echo "$cmd1 | $cmd2" echo "$cmd1 | $cmd2"
output=$($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 "actual blocknumber (exposed to the EVM) would not increase."
echo "" echo ""
echo "### Transactions in RLP form" echo "#### Transactions in RLP form"
echo "" echo ""
echo "It is possible to provide already-signed transactions as input to, using an \`input.txs\` which ends with the \`rlp\` suffix." 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 "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 "to use the evm to go from \`json\` input to \`rlp\` input."
echo "" 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\`.:" 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:" 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" demo "cat signed_txs.rlp"
echo "We can use \`rlpdump\` to check what the contents are: " 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 ) rlpdump -hex $(cat signed_txs.rlp | jq -r )
echo "$ticks" echo "$ticks"
echo "Now, we can now use those (or any other already signed transactions), as input, like so: " 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 "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 "And we can now finally check that they match."
echo "$ticks" echo "$ticks"

@ -95,44 +95,71 @@ func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *ty
return beacon.verifyHeader(chain, header, parent) return beacon.verifyHeader(chain, header, parent)
} }
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers // errOut constructs an error channel with prefilled errors inside.
// concurrently. The method returns a quit channel to abort the operations and func errOut(n int, err error) chan error {
// a results channel to retrieve the async verifications. errs := make(chan error, n)
// VerifyHeaders expect the headers to be ordered and continuous. for i := 0; i < n; i++ {
func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { errs <- err
if !beacon.IsPoSHeader(headers[len(headers)-1]) { }
return beacon.ethone.VerifyHeaders(chain, headers, seals) 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 ( var (
preHeaders []*types.Header preHeaders = headers
postHeaders []*types.Header postHeaders []*types.Header
preSeals []bool td = new(big.Int).Set(ptd)
tdPassed bool
) )
for index, header := range headers { for i, header := range headers {
if beacon.IsPoSHeader(header) { if tdPassed {
preHeaders = headers[:index] preHeaders = headers[:i]
postHeaders = headers[index:] postHeaders = headers[i:]
preSeals = seals[:index]
break 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 { 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) return beacon.verifyHeaders(chain, headers, nil)
} }
// The transition point exists in the middle, separate the headers // The transition point exists in the middle, separate the headers
// into two batches and apply different verification rules for them. // into two batches and apply different verification rules for them.
var ( var (
@ -144,16 +171,9 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [
old, new, out = 0, len(preHeaders), 0 old, new, out = 0, len(preHeaders), 0
errors = make([]error, len(headers)) errors = make([]error, len(headers))
done = make([]bool, 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]) 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 // Collect the results
for { for {
for ; done[out]; out++ { for ; done[out]; out++ {
@ -181,33 +201,6 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [
return abort, results 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 // VerifyUncles verifies that the given block's uncles conform to the consensus
// rules of the Ethereum consensus engine. // rules of the Ethereum consensus engine.
func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { 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. // IsTTDReached checks if the TotalTerminalDifficulty has been surpassed on the `parentHash` block.
// It depends on the parentHash already being stored in the database. // 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. // 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 { if chain.Config().TerminalTotalDifficulty == nil {
return false, nil return false, nil
} }
td := chain.GetTd(parentHash, number) td := chain.GetTd(parentHash, parentNumber)
if td == nil { if td == nil {
return false, consensus.ErrUnknownAncestor return false, consensus.ErrUnknownAncestor
} }

@ -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)
}
}
}

@ -17,7 +17,6 @@
package core package core
import ( import (
"encoding/json"
"math/big" "math/big"
"runtime" "runtime"
"testing" "testing"
@ -135,31 +134,29 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
config := *params.TestChainConfig config := *params.TestChainConfig
gspec = &Genesis{Config: &config} gspec = &Genesis{Config: &config}
engine = beacon.New(ethash.NewFaker()) engine = beacon.New(ethash.NewFaker())
td := int(params.GenesisDifficulty.Uint64())
td := 0
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil) genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil)
for _, block := range preBlocks { for _, block := range blocks {
// calculate td // calculate td
td += int(block.Difficulty().Uint64()) td += int(block.Difficulty().Uint64())
} }
preBlocks = blocks preBlocks = blocks
gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td)) 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 // Assemble header batch
preHeaders := make([]*types.Header, len(preBlocks)) preHeaders := make([]*types.Header, len(preBlocks))
for i, block := range preBlocks { for i, block := range preBlocks {
preHeaders[i] = block.Header() preHeaders[i] = block.Header()
t.Logf("Pre-merge header: %d", block.NumberU64())
blob, _ := json.Marshal(block.Header())
t.Logf("Log header before the merging %d: %v", block.NumberU64(), string(blob))
} }
postHeaders := make([]*types.Header, len(postBlocks)) postHeaders := make([]*types.Header, len(postBlocks))
for i, block := range postBlocks { for i, block := range postBlocks {
postHeaders[i] = block.Header() postHeaders[i] = block.Header()
t.Logf("Post-merge header: %d", block.NumberU64())
blob, _ := json.Marshal(block.Header())
t.Logf("Log header after the merging %d: %v", block.NumberU64(), string(blob))
} }
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces // 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) chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil)
@ -172,15 +169,15 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
select { select {
case result := <-results: case result := <-results:
if result != nil { 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): 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 // Make sure no more data is returned
select { select {
case result := <-results: 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): case <-time.After(25 * time.Millisecond):
} }
chain.InsertChain(preBlocks[i : i+1]) chain.InsertChain(preBlocks[i : i+1])
@ -197,7 +194,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
select { select {
case result := <-results: case result := <-results:
if result != nil { 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): case <-time.After(time.Second):
t.Fatalf("test %d: verification timeout", i) 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 // Make sure no more data is returned
select { select {
case result := <-results: 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): case <-time.After(25 * time.Millisecond):
} }
chain.InsertBlockWithoutSetHead(postBlocks[i]) chain.InsertBlockWithoutSetHead(postBlocks[i])

@ -1959,7 +1959,8 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
Alloc: GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, Alloc: GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
mergeBlock = math.MaxInt32
) )
// Generate and import the canonical chain // Generate and import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) 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 // Activate the transition since genesis if required
if mergePoint == 0 { if mergePoint == 0 {
mergeBlock = 0
merger.ReachTTD() merger.ReachTTD()
merger.FinalizePoS() merger.FinalizePoS()
@ -1982,6 +1984,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
t.Fatalf("failed to create tx: %v", err) t.Fatalf("failed to create tx: %v", err)
} }
gen.AddTx(tx) gen.AddTx(tx)
if int(gen.header.Number.Uint64()) >= mergeBlock {
gen.SetPoS()
}
nonce++ nonce++
}) })
if n, err := chain.InsertChain(blocks); err != nil { if n, err := chain.InsertChain(blocks); err != nil {
@ -2006,7 +2011,10 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
merger.ReachTTD() merger.ReachTTD()
merger.FinalizePoS() merger.FinalizePoS()
// Set the terminal total difficulty in the config // 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 // Generate the sidechain
@ -2018,6 +2026,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
parent := blocks[parentIndex] parent := blocks[parentIndex]
fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) { fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2}) b.SetCoinbase(common.Address{2})
if int(b.header.Number.Uint64()) >= mergeBlock {
b.SetPoS()
}
}) })
// Prepend the parent(s) // Prepend the parent(s)
var sidechain []*types.Block var sidechain []*types.Block
@ -2226,25 +2237,45 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
Config: &chainConfig, Config: &chainConfig,
} }
engine = beacon.New(ethash.NewFaker()) engine = beacon.New(ethash.NewFaker())
mergeBlock = uint64(math.MaxUint64)
) )
// Apply merging since genesis // Apply merging since genesis
if mergeHeight == 0 { if mergeHeight == 0 {
genesis.Config.TerminalTotalDifficulty = big.NewInt(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 // Apply merging after the first segment
if mergeHeight == 1 { 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 // 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) { blocks3, receipts3 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 64, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1}) b.SetCoinbase(common.Address{1})
b.OffsetTime(-9) // Time shifted, difficulty shouldn't be changed 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 // Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
if err != nil { if err != nil {
@ -2268,7 +2299,10 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
for _, block := range blocks { for _, block := range blocks {
headers = append(headers, block.Header()) 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 return err
} }
asserter = func(t *testing.T, block *types.Block) { 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 { for _, block := range blocks {
headers = append(headers, block.Header()) headers = append(headers, block.Header())
} }
_, err := chain.InsertHeaderChain(headers, 1) i, err := chain.InsertHeaderChain(headers, 1)
if err != nil { if err != nil {
return err return fmt.Errorf("index %d: %w", i, err)
} }
_, err = chain.InsertReceiptChain(blocks, receipts, 0) _, err = chain.InsertReceiptChain(blocks, receipts, 0)
return err return err
@ -2296,8 +2330,11 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
} }
} else { } else {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error { inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err := chain.InsertChain(blocks) i, err := chain.InsertChain(blocks)
return err if err != nil {
return fmt.Errorf("index %d: %w", i, err)
}
return nil
} }
asserter = func(t *testing.T, block *types.Block) { asserter = func(t *testing.T, block *types.Block) {
if chain.CurrentBlock().Hash() != block.Hash() { if chain.CurrentBlock().Hash() != block.Hash() {

@ -80,6 +80,11 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
b.header.Difficulty = diff 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 // addTx adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address. // been set, the block's coinbase is set to the zero address.
// //

@ -852,11 +852,11 @@ func TestInvalidBloom(t *testing.T) {
func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(100) 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) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty())
var ( var (
api = NewConsensusAPI(ethservice) api = NewConsensusAPI(ethservice)
parent = preMergeBlocks[len(preMergeBlocks)-1] parent = preMergeBlocks[len(preMergeBlocks)-1]

@ -44,7 +44,7 @@ var (
testBalance = big.NewInt(2e18) 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 config := *params.AllEthashProtocolChanges
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: &config, Config: &config,
@ -53,21 +53,33 @@ func generatePreMergeChain(n int) (*core.Genesis, []*types.Header, []*types.Bloc
Timestamp: 9000, Timestamp: 9000,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), n, nil) // Pre-merge blocks
totalDifficulty := big.NewInt(0) db, preBLocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), pre, nil)
totalDifficulty := new(big.Int).Set(params.GenesisDifficulty)
var headers []*types.Header var preHeaders []*types.Header
for _, b := range blocks { for _, b := range preBLocks {
totalDifficulty.Add(totalDifficulty, b.Difficulty()) totalDifficulty.Add(totalDifficulty, b.Difficulty())
headers = append(headers, b.Header()) preHeaders = append(preHeaders, b.Header())
} }
config.TerminalTotalDifficulty = totalDifficulty 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) { func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
genesis, headers, blocks := generatePreMergeChain(10) genesis, headers, blocks, _, _ := generatePreMergeChain(10, 0)
n, lesService := startLesService(t, genesis, headers) n, lesService := startLesService(t, genesis, headers)
defer n.Close() defer n.Close()
@ -83,21 +95,21 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
} }
func TestExecutePayloadV1(t *testing.T) { func TestExecutePayloadV1(t *testing.T) {
genesis, headers, blocks := generatePreMergeChain(10) genesis, headers, _, _, postBlocks := generatePreMergeChain(10, 2)
n, lesService := startLesService(t, genesis, headers[:9]) n, lesService := startLesService(t, genesis, headers)
lesService.Merger().ReachTTD() lesService.Merger().ReachTTD()
defer n.Close() defer n.Close()
api := NewConsensusAPI(lesService) api := NewConsensusAPI(lesService)
fcState := beacon.ForkchoiceStateV1{ fcState := beacon.ForkchoiceStateV1{
HeadBlockHash: blocks[8].Hash(), HeadBlockHash: postBlocks[0].Hash(),
SafeBlockHash: common.Hash{}, SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{},
} }
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Errorf("Failed to update head %v", err) t.Errorf("Failed to update head %v", err)
} }
block := blocks[9] block := postBlocks[0]
fakeBlock := types.NewBlock(&types.Header{ fakeBlock := types.NewBlock(&types.Header{
ParentHash: block.ParentHash(), ParentHash: block.ParentHash(),

@ -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, // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range,
// using 4.6 TGas // using 4.6 TGas
bt.skipLoad(`.*randomStatetest94.json.*`) bt.skipLoad(`.*randomStatetest94.json.*`)
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
if err := bt.checkFailure(t, test.Run(false)); err != nil { if err := bt.checkFailure(t, test.Run(false)); err != nil {
t.Errorf("test without snapshotter failed: %v", err) t.Errorf("test without snapshotter failed: %v", err)

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus" "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/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -120,6 +121,9 @@ func (t *BlockTest) Run(snapshotter bool) error {
} else { } else {
engine = ethash.NewShared() engine = ethash.NewShared()
} }
// Wrap the original engine within the beacon-engine
engine = beacon.New(engine)
cache := &core.CacheConfig{TrieCleanLimit: 0} cache := &core.CacheConfig{TrieCleanLimit: 0}
if snapshotter { if snapshotter {
cache.SnapshotLimit = 1 cache.SnapshotLimit = 1

@ -197,6 +197,24 @@ var Forks = map[string]*params.ChainConfig{
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
ArrowGlacierBlock: 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": { "GrayGlacier": {
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
@ -213,7 +231,7 @@ var Forks = map[string]*params.ChainConfig{
ArrowGlacierBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0),
}, },
"Merged": { "Merge": {
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0), EIP150Block: big.NewInt(0),

@ -56,14 +56,12 @@ func TestState(t *testing.T) {
// Uses 1GB RAM per tested fork // Uses 1GB RAM per tested fork
st.skipLoad(`^stStaticCall/static_Call1MB`) st.skipLoad(`^stStaticCall/static_Call1MB`)
// Not yet supported TODO
st.skipLoad(`^stEIP3540/`)
st.skipLoad(`^stEIP3860/`)
// Broken tests: // Broken tests:
// Expected failures: // 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 Istanbul, older tests were moved into LegacyTests
for _, dir := range []string{ for _, dir := range []string{

@ -249,10 +249,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
context.GetHash = vmTestBlockHash context.GetHash = vmTestBlockHash
context.BaseFee = baseFee context.BaseFee = baseFee
context.Random = nil context.Random = nil
if config.IsLondon(new(big.Int)) && t.json.Env.Random != nil { if config.IsLondon(new(big.Int)) {
rnd := common.BigToHash(t.json.Env.Random) if t.json.Env.Random != nil {
context.Random = &rnd rnd := common.BigToHash(t.json.Env.Random)
context.Random = &rnd
}
context.Difficulty = big.NewInt(0) 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) evm := vm.NewEVM(context, txContext, statedb, config, vmconfig)
// Execute the message. // Execute the message.

@ -1 +1 @@
Subproject commit a380655e5ffab1a5ea0f4d860224bdb19013f06a Subproject commit 24fa31adb30f71ee700b27decb5204e53a11d9f3
Loading…
Cancel
Save