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.
This commit is contained in:
Martin Holst Swende 2022-12-20 09:56:52 -05:00 committed by GitHub
parent 79a478bb61
commit b818e73ef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 687 additions and 447 deletions

View File

@ -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 has to be supported are
```
--trace Output full trace logs to files <txhash>.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)
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
### Error codes and output
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
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,13 +268,15 @@ 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
@ -143,7 +297,7 @@ Output:
"result": {
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13",
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d",
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"receipts": [
@ -165,12 +319,14 @@ Output:
"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:
@ -180,14 +336,14 @@ Mining rewards and ommer rewards might need to be added. This is how those are a
- 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,7 +364,7 @@ Example:
]
}
```
When applying this, using a reward of `0x80`
When applying this, using a reward of `0x08`
Output:
```json
{
@ -225,7 +381,7 @@ Output:
}
}
```
### 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:
@ -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
```
### 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:
```
./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,34 +403,24 @@ 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.
@ -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
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:
@ -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:
```
./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.
And we can now finally check that they match.
```

View File

@ -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",

View File

@ -20,10 +20,24 @@ function tick(){
echo "$ticks"
}
cat << EOF
## EVM state transition tool
function code(){
echo "$ticks$1"
}
The \`evm t8n\` tool is a stateless state transition utility. It is a utility
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
## 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\."`
```
EOF
./evm t8n -h | grep "\-\-trace\.\|\-\-output\.\|\-\-state\.\|\-\-input"
cat << "EOF"
```
#### Objects
$(tick)
The transition tool uses JSON objects to read and write data related to the transition operation. The
following object definitions are required.
### Error codes and output
##### `alloc`
All logging should happen against the \`stderr\`.
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
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"

View File

@ -95,44 +95,71 @@ func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *ty
return beacon.verifyHeader(chain, header, parent)
}
// 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 = headers
postHeaders []*types.Header
td = new(big.Int).Set(ptd)
tdPassed bool
)
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) {
if !beacon.IsPoSHeader(headers[len(headers)-1]) {
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)
}
var (
preHeaders []*types.Header
postHeaders []*types.Header
preSeals []bool
)
for index, header := range headers {
if beacon.IsPoSHeader(header) {
preHeaders = headers[:index]
postHeaders = headers[index:]
preSeals = seals[:index]
break
}
}
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
}

View File

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

View File

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

View File

@ -1960,6 +1960,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
BaseFee: big.NewInt(params.InitialBaseFee),
}
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
@ -2227,24 +2238,44 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
Config: &chainConfig,
}
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() {

View File

@ -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.
//

View File

@ -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]

View File

@ -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(),

View File

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

View File

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

View File

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

View File

@ -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{

View File

@ -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 {
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.

@ -1 +1 @@
Subproject commit a380655e5ffab1a5ea0f4d860224bdb19013f06a
Subproject commit 24fa31adb30f71ee700b27decb5204e53a11d9f3