forked from cerc-io/plugeth
cmd/evm: add state transition tool for testing (#20958)
This PR implements the EVM state transition tool, which is intended to be the replacement for our retesteth client implementation. Documentation is present in the cmd/evm/README.md file. Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
dd91c7ce6a
commit
e376d2fb31
268
cmd/evm/README.md
Normal file
268
cmd/evm/README.md
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
## EVM state transition tool
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
## 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 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
|
||||||
|
--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
|
||||||
|
--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)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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`)
|
||||||
|
|
||||||
|
- 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`.
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
### 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
|
||||||
|
```
|
||||||
|
Two resulting files:
|
||||||
|
|
||||||
|
`alloc.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": {
|
||||||
|
"balance": "0xfeed1a9d",
|
||||||
|
"nonce": "0x1"
|
||||||
|
},
|
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0x5ffd4878be161d74",
|
||||||
|
"nonce": "0xac"
|
||||||
|
},
|
||||||
|
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0xa410"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`result.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13",
|
||||||
|
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d",
|
||||||
|
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
|
||||||
|
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"receipts": [
|
||||||
|
{
|
||||||
|
"root": "0x",
|
||||||
|
"status": "0x1",
|
||||||
|
"cumulativeGasUsed": "0x5208",
|
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"logs": null,
|
||||||
|
"transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673",
|
||||||
|
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||||
|
"gasUsed": "0x5208",
|
||||||
|
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"transactionIndex": "0x0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rejected": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
Output:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"alloc": {
|
||||||
|
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": {
|
||||||
|
"balance": "0xfeed1a9d",
|
||||||
|
"nonce": "0x1"
|
||||||
|
},
|
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0x5ffd4878be161d74",
|
||||||
|
"nonce": "0xac"
|
||||||
|
},
|
||||||
|
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0xa410"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13",
|
||||||
|
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d",
|
||||||
|
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
|
||||||
|
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"receipts": [
|
||||||
|
{
|
||||||
|
"root": "0x",
|
||||||
|
"status": "0x1",
|
||||||
|
"cumulativeGasUsed": "0x5208",
|
||||||
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"logs": null,
|
||||||
|
"transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673",
|
||||||
|
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||||
|
"gasUsed": "0x5208",
|
||||||
|
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"transactionIndex": "0x0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rejected": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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`
|
||||||
|
|
||||||
|
To make `state_t8n` apply these, the following inputs are required:
|
||||||
|
|
||||||
|
- `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`.
|
||||||
|
|
||||||
|
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
|
||||||
|
the tool allows for negative uncle reward (distance > 8)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`./testdata/5/env.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"currentDifficulty": "0x20000",
|
||||||
|
"currentGasLimit": "0x750a163df65e8a",
|
||||||
|
"currentNumber": "1",
|
||||||
|
"currentTimestamp": "1000",
|
||||||
|
"ommers": [
|
||||||
|
{"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
|
||||||
|
{"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
When applying this, using a reward of `0x08`
|
||||||
|
Output:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"alloc": {
|
||||||
|
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": {
|
||||||
|
"balance": "0x88"
|
||||||
|
},
|
||||||
|
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": {
|
||||||
|
"balance": "0x70"
|
||||||
|
},
|
||||||
|
"0xcccccccccccccccccccccccccccccccccccccccc": {
|
||||||
|
"balance": "0x60"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 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:
|
||||||
|
```
|
||||||
|
./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
|
||||||
|
|
||||||
|
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 t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace
|
||||||
|
```
|
||||||
|
```
|
||||||
|
cat trace-0.jsonl | grep BLOCKHASH -C2
|
||||||
|
```
|
||||||
|
```
|
||||||
|
{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"depth":1,"refund":0,"opName":"PUSH1","error":""}
|
||||||
|
{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"depth":1,"refund":0,"opName":"BLOCKHASH","error":""}
|
||||||
|
{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"depth":1,"refund":0,"opName":"STOP","error":""}
|
||||||
|
{"output":"","gasUsed":"0x17","time":155861}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
```
|
||||||
|
ERROR(4): getHash(3) invoked, blockhash for that block not provided
|
||||||
|
```
|
||||||
|
Error code: 4
|
||||||
|
### 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 [06-29|11:52:04.934] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||||
|
INFO [06-29|11:52:04.936] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||||
|
INFO [06-29|11:52:04.936] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low"
|
||||||
|
|
||||||
|
```
|
||||||
|
What happened here, is that we first applied two identical transactions, so the second one was rejected.
|
||||||
|
Then, taking the poststate alloc as the input for the next state, we tried again to include
|
||||||
|
the same two transactions: this time, both failed due to too low nonce.
|
||||||
|
|
||||||
|
In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the
|
||||||
|
actual blocknumber (exposed to the EVM) would not increase.
|
||||||
|
|
255
cmd/evm/internal/t8ntool/execution.go
Normal file
255
cmd/evm/internal/t8ntool/execution.go
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package t8ntool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Prestate struct {
|
||||||
|
Env stEnv `json:"env"`
|
||||||
|
Pre core.GenesisAlloc `json:"pre"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutionResult contains the execution status after running a state test, any
|
||||||
|
// error that might have occurred and a dump of the final state if requested.
|
||||||
|
type ExecutionResult struct {
|
||||||
|
StateRoot common.Hash `json:"stateRoot"`
|
||||||
|
TxRoot common.Hash `json:"txRoot"`
|
||||||
|
ReceiptRoot common.Hash `json:"receiptRoot"`
|
||||||
|
LogsHash common.Hash `json:"logsHash"`
|
||||||
|
Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
|
||||||
|
Receipts types.Receipts `json:"receipts"`
|
||||||
|
Rejected []int `json:"rejected,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ommer struct {
|
||||||
|
Delta uint64 `json:"delta"`
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go
|
||||||
|
type stEnv struct {
|
||||||
|
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
|
||||||
|
Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"`
|
||||||
|
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
|
||||||
|
Number uint64 `json:"currentNumber" gencodec:"required"`
|
||||||
|
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
|
||||||
|
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||||
|
Ommers []ommer `json:"ommers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type stEnvMarshaling struct {
|
||||||
|
Coinbase common.UnprefixedAddress
|
||||||
|
Difficulty *math.HexOrDecimal256
|
||||||
|
GasLimit math.HexOrDecimal64
|
||||||
|
Number math.HexOrDecimal64
|
||||||
|
Timestamp math.HexOrDecimal64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a set of transactions to a pre-state
|
||||||
|
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
|
txs types.Transactions, miningReward int64,
|
||||||
|
getTracerFn func(txIndex int) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) {
|
||||||
|
|
||||||
|
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
|
||||||
|
// required blockhashes
|
||||||
|
var hashError error
|
||||||
|
getHash := func(num uint64) common.Hash {
|
||||||
|
if pre.Env.BlockHashes == nil {
|
||||||
|
hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num)
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)]
|
||||||
|
if !ok {
|
||||||
|
hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre)
|
||||||
|
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number))
|
||||||
|
gaspool = new(core.GasPool)
|
||||||
|
blockHash = common.Hash{0x13, 0x37}
|
||||||
|
rejectedTxs []int
|
||||||
|
includedTxs types.Transactions
|
||||||
|
gasUsed = uint64(0)
|
||||||
|
receipts = make(types.Receipts, 0)
|
||||||
|
txIndex = 0
|
||||||
|
)
|
||||||
|
gaspool.AddGas(pre.Env.GasLimit)
|
||||||
|
vmContext := vm.Context{
|
||||||
|
CanTransfer: core.CanTransfer,
|
||||||
|
Transfer: core.Transfer,
|
||||||
|
Coinbase: pre.Env.Coinbase,
|
||||||
|
BlockNumber: new(big.Int).SetUint64(pre.Env.Number),
|
||||||
|
Time: new(big.Int).SetUint64(pre.Env.Timestamp),
|
||||||
|
Difficulty: pre.Env.Difficulty,
|
||||||
|
GasLimit: pre.Env.GasLimit,
|
||||||
|
GetHash: getHash,
|
||||||
|
// GasPrice and Origin needs to be set per transaction
|
||||||
|
}
|
||||||
|
// If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's
|
||||||
|
// done in StateProcessor.Process(block, ...), right before transactions are applied.
|
||||||
|
if chainConfig.DAOForkSupport &&
|
||||||
|
chainConfig.DAOForkBlock != nil &&
|
||||||
|
chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 {
|
||||||
|
misc.ApplyDAOHardFork(statedb)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tx := range txs {
|
||||||
|
msg, err := tx.AsMessage(signer)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "error", err)
|
||||||
|
rejectedTxs = append(rejectedTxs, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tracer, err := getTracerFn(txIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
vmConfig.Tracer = tracer
|
||||||
|
vmConfig.Debug = (tracer != nil)
|
||||||
|
statedb.Prepare(tx.Hash(), blockHash, txIndex)
|
||||||
|
vmContext.GasPrice = msg.GasPrice()
|
||||||
|
vmContext.Origin = msg.From()
|
||||||
|
|
||||||
|
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
|
||||||
|
snapshot := statedb.Snapshot()
|
||||||
|
// (ret []byte, usedGas uint64, failed bool, err error)
|
||||||
|
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
||||||
|
if err != nil {
|
||||||
|
statedb.RevertToSnapshot(snapshot)
|
||||||
|
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err)
|
||||||
|
rejectedTxs = append(rejectedTxs, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
includedTxs = append(includedTxs, tx)
|
||||||
|
if hashError != nil {
|
||||||
|
return nil, nil, NewError(ErrorMissingBlockhash, hashError)
|
||||||
|
}
|
||||||
|
gasUsed += msgResult.UsedGas
|
||||||
|
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
|
||||||
|
{
|
||||||
|
var root []byte
|
||||||
|
if chainConfig.IsByzantium(vmContext.BlockNumber) {
|
||||||
|
statedb.Finalise(true)
|
||||||
|
} else {
|
||||||
|
root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
receipt := types.NewReceipt(root, msgResult.Failed(), gasUsed)
|
||||||
|
receipt.TxHash = tx.Hash()
|
||||||
|
receipt.GasUsed = msgResult.UsedGas
|
||||||
|
// if the transaction created a contract, store the creation address in the receipt.
|
||||||
|
if msg.To() == nil {
|
||||||
|
receipt.ContractAddress = crypto.CreateAddress(evm.Context.Origin, tx.Nonce())
|
||||||
|
}
|
||||||
|
// Set the receipt logs and create a bloom for filtering
|
||||||
|
receipt.Logs = statedb.GetLogs(tx.Hash())
|
||||||
|
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
||||||
|
// These three are non-consensus fields
|
||||||
|
//receipt.BlockHash
|
||||||
|
//receipt.BlockNumber =
|
||||||
|
receipt.TransactionIndex = uint(txIndex)
|
||||||
|
receipts = append(receipts, receipt)
|
||||||
|
}
|
||||||
|
txIndex++
|
||||||
|
}
|
||||||
|
statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber))
|
||||||
|
// Add mining reward?
|
||||||
|
if miningReward > 0 {
|
||||||
|
// Add mining reward. The mining reward may be `0`, which only makes a difference in the cases
|
||||||
|
// where
|
||||||
|
// - the coinbase suicided, or
|
||||||
|
// - there are only 'bad' transactions, which aren't executed. In those cases,
|
||||||
|
// the coinbase gets no txfee, so isn't created, and thus needs to be touched
|
||||||
|
var (
|
||||||
|
blockReward = big.NewInt(miningReward)
|
||||||
|
minerReward = new(big.Int).Set(blockReward)
|
||||||
|
perOmmer = new(big.Int).Div(blockReward, big.NewInt(32))
|
||||||
|
)
|
||||||
|
for _, ommer := range pre.Env.Ommers {
|
||||||
|
// Add 1/32th for each ommer included
|
||||||
|
minerReward.Add(minerReward, perOmmer)
|
||||||
|
// Add (8-delta)/8
|
||||||
|
reward := big.NewInt(8)
|
||||||
|
reward.Sub(reward, big.NewInt(0).SetUint64(ommer.Delta))
|
||||||
|
reward.Mul(reward, blockReward)
|
||||||
|
reward.Div(reward, big.NewInt(8))
|
||||||
|
statedb.AddBalance(ommer.Address, reward)
|
||||||
|
}
|
||||||
|
statedb.AddBalance(pre.Env.Coinbase, minerReward)
|
||||||
|
}
|
||||||
|
// Commit block
|
||||||
|
root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Could not commit state: %v", err)
|
||||||
|
return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
|
||||||
|
}
|
||||||
|
execRs := &ExecutionResult{
|
||||||
|
StateRoot: root,
|
||||||
|
TxRoot: types.DeriveSha(includedTxs),
|
||||||
|
ReceiptRoot: types.DeriveSha(receipts),
|
||||||
|
Bloom: types.CreateBloom(receipts),
|
||||||
|
LogsHash: rlpHash(statedb.Logs()),
|
||||||
|
Receipts: receipts,
|
||||||
|
Rejected: rejectedTxs,
|
||||||
|
}
|
||||||
|
return statedb, execRs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB {
|
||||||
|
sdb := state.NewDatabase(db)
|
||||||
|
statedb, _ := state.New(common.Hash{}, sdb, nil)
|
||||||
|
for addr, a := range accounts {
|
||||||
|
statedb.SetCode(addr, a.Code)
|
||||||
|
statedb.SetNonce(addr, a.Nonce)
|
||||||
|
statedb.SetBalance(addr, a.Balance)
|
||||||
|
for k, v := range a.Storage {
|
||||||
|
statedb.SetState(addr, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Commit and re-open to start with a clean state.
|
||||||
|
root, _ := statedb.Commit(false)
|
||||||
|
statedb, _ = state.New(root, sdb, nil)
|
||||||
|
return statedb
|
||||||
|
}
|
||||||
|
|
||||||
|
func rlpHash(x interface{}) (h common.Hash) {
|
||||||
|
hw := sha3.NewLegacyKeccak256()
|
||||||
|
rlp.Encode(hw, x)
|
||||||
|
hw.Sum(h[:0])
|
||||||
|
return h
|
||||||
|
}
|
99
cmd/evm/internal/t8ntool/flags.go
Normal file
99
cmd/evm/internal/t8ntool/flags.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package t8ntool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TraceFlag = cli.BoolFlag{
|
||||||
|
Name: "trace",
|
||||||
|
Usage: "Output full trace logs to files <txhash>.jsonl",
|
||||||
|
}
|
||||||
|
TraceDisableMemoryFlag = cli.BoolFlag{
|
||||||
|
Name: "trace.nomemory",
|
||||||
|
Usage: "Disable full memory dump in traces",
|
||||||
|
}
|
||||||
|
TraceDisableStackFlag = cli.BoolFlag{
|
||||||
|
Name: "trace.nostack",
|
||||||
|
Usage: "Disable stack output in traces",
|
||||||
|
}
|
||||||
|
OutputAllocFlag = cli.StringFlag{
|
||||||
|
Name: "output.alloc",
|
||||||
|
Usage: "Determines where to put the `alloc` of the post-state.\n" +
|
||||||
|
"\t`stdout` - into the stdout output\n" +
|
||||||
|
"\t`stderr` - into the stderr output\n" +
|
||||||
|
"\t<file> - into the file <file> ",
|
||||||
|
Value: "alloc.json",
|
||||||
|
}
|
||||||
|
OutputResultFlag = cli.StringFlag{
|
||||||
|
Name: "output.result",
|
||||||
|
Usage: "Determines where to put the `result` (stateroot, txroot etc) of the post-state.\n" +
|
||||||
|
"\t`stdout` - into the stdout output\n" +
|
||||||
|
"\t`stderr` - into the stderr output\n" +
|
||||||
|
"\t<file> - into the file <file> ",
|
||||||
|
Value: "result.json",
|
||||||
|
}
|
||||||
|
InputAllocFlag = cli.StringFlag{
|
||||||
|
Name: "input.alloc",
|
||||||
|
Usage: "`stdin` or file name of where to find the prestate alloc to use.",
|
||||||
|
Value: "alloc.json",
|
||||||
|
}
|
||||||
|
InputEnvFlag = cli.StringFlag{
|
||||||
|
Name: "input.env",
|
||||||
|
Usage: "`stdin` or file name of where to find the prestate env to use.",
|
||||||
|
Value: "env.json",
|
||||||
|
}
|
||||||
|
InputTxsFlag = cli.StringFlag{
|
||||||
|
Name: "input.txs",
|
||||||
|
Usage: "`stdin` or file name of where to find the transactions to apply.",
|
||||||
|
Value: "txs.json",
|
||||||
|
}
|
||||||
|
RewardFlag = cli.Int64Flag{
|
||||||
|
Name: "state.reward",
|
||||||
|
Usage: "Mining reward. Set to -1 to disable",
|
||||||
|
Value: 0,
|
||||||
|
}
|
||||||
|
ChainIDFlag = cli.Int64Flag{
|
||||||
|
Name: "state.chainid",
|
||||||
|
Usage: "ChainID to use",
|
||||||
|
Value: 1,
|
||||||
|
}
|
||||||
|
ForknameFlag = cli.StringFlag{
|
||||||
|
Name: "state.fork",
|
||||||
|
Usage: fmt.Sprintf("Name of ruleset to use."+
|
||||||
|
"\n\tAvailable forknames:"+
|
||||||
|
"\n\t %v"+
|
||||||
|
"\n\tAvailable extra eips:"+
|
||||||
|
"\n\t %v"+
|
||||||
|
"\n\tSyntax <forkname>(+ExtraEip)",
|
||||||
|
strings.Join(tests.AvailableForks(), "\n\t "),
|
||||||
|
strings.Join(vm.ActivateableEips(), ", ")),
|
||||||
|
Value: "Istanbul",
|
||||||
|
}
|
||||||
|
VerbosityFlag = cli.IntFlag{
|
||||||
|
Name: "verbosity",
|
||||||
|
Usage: "sets the verbosity level",
|
||||||
|
Value: 3,
|
||||||
|
}
|
||||||
|
)
|
80
cmd/evm/internal/t8ntool/gen_stenv.go
Normal file
80
cmd/evm/internal/t8ntool/gen_stenv.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package t8ntool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*stEnvMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (s stEnv) MarshalJSON() ([]byte, error) {
|
||||||
|
type stEnv struct {
|
||||||
|
Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
|
||||||
|
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
|
||||||
|
GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
|
||||||
|
Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
|
||||||
|
Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||||
|
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||||
|
Ommers []ommer `json:"ommers,omitempty"`
|
||||||
|
}
|
||||||
|
var enc stEnv
|
||||||
|
enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
|
||||||
|
enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty)
|
||||||
|
enc.GasLimit = math.HexOrDecimal64(s.GasLimit)
|
||||||
|
enc.Number = math.HexOrDecimal64(s.Number)
|
||||||
|
enc.Timestamp = math.HexOrDecimal64(s.Timestamp)
|
||||||
|
enc.BlockHashes = s.BlockHashes
|
||||||
|
enc.Ommers = s.Ommers
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (s *stEnv) UnmarshalJSON(input []byte) error {
|
||||||
|
type stEnv struct {
|
||||||
|
Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
|
||||||
|
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
|
||||||
|
GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
|
||||||
|
Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
|
||||||
|
Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||||
|
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
|
||||||
|
Ommers []ommer `json:"ommers,omitempty"`
|
||||||
|
}
|
||||||
|
var dec stEnv
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.Coinbase == nil {
|
||||||
|
return errors.New("missing required field 'currentCoinbase' for stEnv")
|
||||||
|
}
|
||||||
|
s.Coinbase = common.Address(*dec.Coinbase)
|
||||||
|
if dec.Difficulty == nil {
|
||||||
|
return errors.New("missing required field 'currentDifficulty' for stEnv")
|
||||||
|
}
|
||||||
|
s.Difficulty = (*big.Int)(dec.Difficulty)
|
||||||
|
if dec.GasLimit == nil {
|
||||||
|
return errors.New("missing required field 'currentGasLimit' for stEnv")
|
||||||
|
}
|
||||||
|
s.GasLimit = uint64(*dec.GasLimit)
|
||||||
|
if dec.Number == nil {
|
||||||
|
return errors.New("missing required field 'currentNumber' for stEnv")
|
||||||
|
}
|
||||||
|
s.Number = uint64(*dec.Number)
|
||||||
|
if dec.Timestamp == nil {
|
||||||
|
return errors.New("missing required field 'currentTimestamp' for stEnv")
|
||||||
|
}
|
||||||
|
s.Timestamp = uint64(*dec.Timestamp)
|
||||||
|
if dec.BlockHashes != nil {
|
||||||
|
s.BlockHashes = dec.BlockHashes
|
||||||
|
}
|
||||||
|
if dec.Ommers != nil {
|
||||||
|
s.Ommers = dec.Ommers
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
276
cmd/evm/internal/t8ntool/transition.go
Normal file
276
cmd/evm/internal/t8ntool/transition.go
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package t8ntool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorEVM = 2
|
||||||
|
ErrorVMConfig = 3
|
||||||
|
ErrorMissingBlockhash = 4
|
||||||
|
|
||||||
|
ErrorJson = 10
|
||||||
|
ErrorIO = 11
|
||||||
|
|
||||||
|
stdinSelector = "stdin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NumberedError struct {
|
||||||
|
errorCode int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(errorCode int, err error) *NumberedError {
|
||||||
|
return &NumberedError{errorCode, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberedError) Error() string {
|
||||||
|
return fmt.Sprintf("ERROR(%d): %v", n.errorCode, n.err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberedError) Code() int {
|
||||||
|
return n.errorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
Alloc core.GenesisAlloc `json:"alloc,omitempty"`
|
||||||
|
Env *stEnv `json:"env,omitempty"`
|
||||||
|
Txs types.Transactions `json:"txs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main(ctx *cli.Context) error {
|
||||||
|
// Configure the go-ethereum logger
|
||||||
|
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
||||||
|
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
|
||||||
|
log.Root().SetHandler(glogger)
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
tracer vm.Tracer
|
||||||
|
)
|
||||||
|
var getTracer func(txIndex int) (vm.Tracer, error)
|
||||||
|
|
||||||
|
if ctx.Bool(TraceFlag.Name) {
|
||||||
|
// Configure the EVM logger
|
||||||
|
logConfig := &vm.LogConfig{
|
||||||
|
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
|
||||||
|
DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name),
|
||||||
|
Debug: true,
|
||||||
|
}
|
||||||
|
var prevFile *os.File
|
||||||
|
// This one closes the last file
|
||||||
|
defer func() {
|
||||||
|
if prevFile != nil {
|
||||||
|
prevFile.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
getTracer = func(txIndex int) (vm.Tracer, error) {
|
||||||
|
if prevFile != nil {
|
||||||
|
prevFile.Close()
|
||||||
|
}
|
||||||
|
traceFile, err := os.Create(fmt.Sprintf("trace-%d.jsonl", txIndex))
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
|
||||||
|
}
|
||||||
|
prevFile = traceFile
|
||||||
|
return vm.NewJSONLogger(logConfig, traceFile), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getTracer = func(txIndex int) (tracer vm.Tracer, err error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We need to load three things: alloc, env and transactions. May be either in
|
||||||
|
// stdin input or in files.
|
||||||
|
// Check if anything needs to be read from stdin
|
||||||
|
var (
|
||||||
|
prestate Prestate
|
||||||
|
txs types.Transactions // txs to apply
|
||||||
|
allocStr = ctx.String(InputAllocFlag.Name)
|
||||||
|
|
||||||
|
envStr = ctx.String(InputEnvFlag.Name)
|
||||||
|
txStr = ctx.String(InputTxsFlag.Name)
|
||||||
|
inputData = &input{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
|
||||||
|
decoder := json.NewDecoder(os.Stdin)
|
||||||
|
decoder.Decode(inputData)
|
||||||
|
}
|
||||||
|
if allocStr != stdinSelector {
|
||||||
|
inFile, err := os.Open(allocStr)
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err))
|
||||||
|
}
|
||||||
|
defer inFile.Close()
|
||||||
|
decoder := json.NewDecoder(inFile)
|
||||||
|
if err := decoder.Decode(&inputData.Alloc); err != nil {
|
||||||
|
return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envStr != stdinSelector {
|
||||||
|
inFile, err := os.Open(envStr)
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorIO, fmt.Errorf("failed reading env file: %v", err))
|
||||||
|
}
|
||||||
|
defer inFile.Close()
|
||||||
|
decoder := json.NewDecoder(inFile)
|
||||||
|
var env stEnv
|
||||||
|
if err := decoder.Decode(&env); err != nil {
|
||||||
|
return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling env-file: %v", err))
|
||||||
|
}
|
||||||
|
inputData.Env = &env
|
||||||
|
}
|
||||||
|
|
||||||
|
if txStr != stdinSelector {
|
||||||
|
inFile, err := os.Open(txStr)
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err))
|
||||||
|
}
|
||||||
|
defer inFile.Close()
|
||||||
|
decoder := json.NewDecoder(inFile)
|
||||||
|
var txs types.Transactions
|
||||||
|
if err := decoder.Decode(&txs); err != nil {
|
||||||
|
return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err))
|
||||||
|
}
|
||||||
|
inputData.Txs = txs
|
||||||
|
}
|
||||||
|
|
||||||
|
prestate.Pre = inputData.Alloc
|
||||||
|
prestate.Env = *inputData.Env
|
||||||
|
txs = inputData.Txs
|
||||||
|
|
||||||
|
// Iterate over all the tests, run them and aggregate the results
|
||||||
|
vmConfig := vm.Config{
|
||||||
|
Tracer: tracer,
|
||||||
|
Debug: (tracer != nil),
|
||||||
|
}
|
||||||
|
// Construct the chainconfig
|
||||||
|
var chainConfig *params.ChainConfig
|
||||||
|
if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {
|
||||||
|
return NewError(ErrorVMConfig, fmt.Errorf("Failed constructing chain configuration: %v", err))
|
||||||
|
} else {
|
||||||
|
chainConfig = cConf
|
||||||
|
vmConfig.ExtraEips = extraEips
|
||||||
|
}
|
||||||
|
// Set the chain id
|
||||||
|
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
|
||||||
|
|
||||||
|
// Run the test and aggregate the result
|
||||||
|
state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Dump the excution result
|
||||||
|
//postAlloc := state.DumpGenesisFormat(false, false, false)
|
||||||
|
collector := make(Alloc)
|
||||||
|
state.DumpToCollector(collector, false, false, false, nil, -1)
|
||||||
|
return dispatchOutput(ctx, result, collector)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Alloc map[common.Address]core.GenesisAccount
|
||||||
|
|
||||||
|
func (g Alloc) OnRoot(common.Hash) {}
|
||||||
|
|
||||||
|
func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) {
|
||||||
|
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10)
|
||||||
|
var storage map[common.Hash]common.Hash
|
||||||
|
if dumpAccount.Storage != nil {
|
||||||
|
storage = make(map[common.Hash]common.Hash)
|
||||||
|
for k, v := range dumpAccount.Storage {
|
||||||
|
storage[k] = common.HexToHash(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
genesisAccount := core.GenesisAccount{
|
||||||
|
Code: common.FromHex(dumpAccount.Code),
|
||||||
|
Storage: storage,
|
||||||
|
Balance: balance,
|
||||||
|
Nonce: dumpAccount.Nonce,
|
||||||
|
}
|
||||||
|
g[addr] = genesisAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveFile marshalls the object to the given file
|
||||||
|
func saveFile(filename string, data interface{}) error {
|
||||||
|
b, err := json.MarshalIndent(data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
|
||||||
|
}
|
||||||
|
if err = ioutil.WriteFile(filename, b, 0644); err != nil {
|
||||||
|
return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||||
|
// files
|
||||||
|
func dispatchOutput(ctx *cli.Context, result *ExecutionResult, alloc Alloc) error {
|
||||||
|
stdOutObject := make(map[string]interface{})
|
||||||
|
stdErrObject := make(map[string]interface{})
|
||||||
|
dispatch := func(fName, name string, obj interface{}) error {
|
||||||
|
switch fName {
|
||||||
|
case "stdout":
|
||||||
|
stdOutObject[name] = obj
|
||||||
|
case "stderr":
|
||||||
|
stdErrObject[name] = obj
|
||||||
|
default: // save to file
|
||||||
|
if err := saveFile(fName, obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := dispatch(ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := dispatch(ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(stdOutObject) > 0 {
|
||||||
|
b, err := json.MarshalIndent(stdOutObject, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
|
||||||
|
}
|
||||||
|
os.Stdout.Write(b)
|
||||||
|
}
|
||||||
|
if len(stdErrObject) > 0 {
|
||||||
|
b, err := json.MarshalIndent(stdErrObject, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
|
||||||
|
}
|
||||||
|
os.Stderr.Write(b)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
@ -126,6 +127,27 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var stateTransitionCommand = cli.Command{
|
||||||
|
Name: "transition",
|
||||||
|
Aliases: []string{"t8n"},
|
||||||
|
Usage: "executes a full state transition",
|
||||||
|
Action: t8ntool.Main,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
t8ntool.TraceFlag,
|
||||||
|
t8ntool.TraceDisableMemoryFlag,
|
||||||
|
t8ntool.TraceDisableStackFlag,
|
||||||
|
t8ntool.OutputAllocFlag,
|
||||||
|
t8ntool.OutputResultFlag,
|
||||||
|
t8ntool.InputAllocFlag,
|
||||||
|
t8ntool.InputEnvFlag,
|
||||||
|
t8ntool.InputTxsFlag,
|
||||||
|
t8ntool.ForknameFlag,
|
||||||
|
t8ntool.ChainIDFlag,
|
||||||
|
t8ntool.RewardFlag,
|
||||||
|
t8ntool.VerbosityFlag,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
BenchFlag,
|
BenchFlag,
|
||||||
@ -156,13 +178,18 @@ func init() {
|
|||||||
disasmCommand,
|
disasmCommand,
|
||||||
runCommand,
|
runCommand,
|
||||||
stateTestCommand,
|
stateTestCommand,
|
||||||
|
stateTransitionCommand,
|
||||||
}
|
}
|
||||||
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
code := 1
|
||||||
|
if ec, ok := err.(*t8ntool.NumberedError); ok {
|
||||||
|
code = ec.Code()
|
||||||
|
}
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
cmd/evm/poststate.json
Normal file
23
cmd/evm/poststate.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"root": "f4157bb27bcb1d1a63001434a249a80948f2e9fe1f53d551244c1dae826b5b23",
|
||||||
|
"accounts": {
|
||||||
|
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": {
|
||||||
|
"balance": "4276951709",
|
||||||
|
"nonce": 1,
|
||||||
|
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
|
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||||
|
},
|
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "6916764286133345652",
|
||||||
|
"nonce": 172,
|
||||||
|
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
|
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||||
|
},
|
||||||
|
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "42500",
|
||||||
|
"nonce": 0,
|
||||||
|
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
|
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
cmd/evm/testdata/1/alloc.json
vendored
Normal file
12
cmd/evm/testdata/1/alloc.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0x5ffd4878be161d74",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0xac",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{
|
||||||
|
"balance": "0xfeedbead",
|
||||||
|
"nonce" : "0x00"
|
||||||
|
}
|
||||||
|
}
|
7
cmd/evm/testdata/1/env.json
vendored
Normal file
7
cmd/evm/testdata/1/env.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
||||||
|
"currentDifficulty": "0x20000",
|
||||||
|
"currentGasLimit": "0x750a163df65e8a",
|
||||||
|
"currentNumber": "1",
|
||||||
|
"currentTimestamp": "1000"
|
||||||
|
}
|
26
cmd/evm/testdata/1/txs.json
vendored
Normal file
26
cmd/evm/testdata/1/txs.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"gas": "0x5208",
|
||||||
|
"gasPrice": "0x2",
|
||||||
|
"hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673",
|
||||||
|
"input": "0x",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb",
|
||||||
|
"s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600",
|
||||||
|
"to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192",
|
||||||
|
"v": "0x1b",
|
||||||
|
"value": "0x1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gas": "0x5208",
|
||||||
|
"gasPrice": "0x2",
|
||||||
|
"hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673",
|
||||||
|
"input": "0x",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb",
|
||||||
|
"s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600",
|
||||||
|
"to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192",
|
||||||
|
"v": "0x1b",
|
||||||
|
"value": "0x1"
|
||||||
|
}
|
||||||
|
]
|
16
cmd/evm/testdata/2/alloc.json
vendored
Normal file
16
cmd/evm/testdata/2/alloc.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
|
||||||
|
"balance" : "0x0de0b6b3a7640000",
|
||||||
|
"code" : "0x6001600053600160006001f0ff00",
|
||||||
|
"nonce" : "0x00",
|
||||||
|
"storage" : {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
|
||||||
|
"balance" : "0x0de0b6b3a7640000",
|
||||||
|
"code" : "0x",
|
||||||
|
"nonce" : "0x00",
|
||||||
|
"storage" : {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
cmd/evm/testdata/2/env.json
vendored
Normal file
7
cmd/evm/testdata/2/env.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
|
||||||
|
"currentDifficulty" : "0x020000",
|
||||||
|
"currentGasLimit" : "0x3b9aca00",
|
||||||
|
"currentNumber" : "0x01",
|
||||||
|
"currentTimestamp" : "0x03e8"
|
||||||
|
}
|
1
cmd/evm/testdata/2/readme.md
vendored
Normal file
1
cmd/evm/testdata/2/readme.md
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
These files examplify a selfdestruct to the `0`-address.
|
14
cmd/evm/testdata/2/txs.json
vendored
Normal file
14
cmd/evm/testdata/2/txs.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"input" : "0x",
|
||||||
|
"gas" : "0x5f5e100",
|
||||||
|
"gasPrice" : "0x1",
|
||||||
|
"nonce" : "0x0",
|
||||||
|
"to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
|
||||||
|
"value" : "0x186a0",
|
||||||
|
"v" : "0x1b",
|
||||||
|
"r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b",
|
||||||
|
"s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28",
|
||||||
|
"hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81"
|
||||||
|
}
|
||||||
|
]
|
16
cmd/evm/testdata/3/alloc.json
vendored
Normal file
16
cmd/evm/testdata/3/alloc.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
|
||||||
|
"balance" : "0x0de0b6b3a7640000",
|
||||||
|
"code" : "0x600140",
|
||||||
|
"nonce" : "0x00",
|
||||||
|
"storage" : {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
|
||||||
|
"balance" : "0x0de0b6b3a7640000",
|
||||||
|
"code" : "0x",
|
||||||
|
"nonce" : "0x00",
|
||||||
|
"storage" : {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
cmd/evm/testdata/3/env.json
vendored
Normal file
8
cmd/evm/testdata/3/env.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
|
||||||
|
"currentDifficulty" : "0x020000",
|
||||||
|
"currentGasLimit" : "0x3b9aca00",
|
||||||
|
"currentNumber" : "0x05",
|
||||||
|
"currentTimestamp" : "0x03e8",
|
||||||
|
"blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"}
|
||||||
|
}
|
2
cmd/evm/testdata/3/readme.md
vendored
Normal file
2
cmd/evm/testdata/3/readme.md
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
These files examplify a transition where a transaction (excuted on block 5) requests
|
||||||
|
the blockhash for block `1`.
|
14
cmd/evm/testdata/3/txs.json
vendored
Normal file
14
cmd/evm/testdata/3/txs.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"input" : "0x",
|
||||||
|
"gas" : "0x5f5e100",
|
||||||
|
"gasPrice" : "0x1",
|
||||||
|
"nonce" : "0x0",
|
||||||
|
"to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
|
||||||
|
"value" : "0x186a0",
|
||||||
|
"v" : "0x1b",
|
||||||
|
"r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b",
|
||||||
|
"s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28",
|
||||||
|
"hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81"
|
||||||
|
}
|
||||||
|
]
|
16
cmd/evm/testdata/4/alloc.json
vendored
Normal file
16
cmd/evm/testdata/4/alloc.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
|
||||||
|
"balance" : "0x0de0b6b3a7640000",
|
||||||
|
"code" : "0x600340",
|
||||||
|
"nonce" : "0x00",
|
||||||
|
"storage" : {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
|
||||||
|
"balance" : "0x0de0b6b3a7640000",
|
||||||
|
"code" : "0x",
|
||||||
|
"nonce" : "0x00",
|
||||||
|
"storage" : {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
cmd/evm/testdata/4/env.json
vendored
Normal file
8
cmd/evm/testdata/4/env.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
|
||||||
|
"currentDifficulty" : "0x020000",
|
||||||
|
"currentGasLimit" : "0x3b9aca00",
|
||||||
|
"currentNumber" : "0x05",
|
||||||
|
"currentTimestamp" : "0x03e8",
|
||||||
|
"blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"}
|
||||||
|
}
|
3
cmd/evm/testdata/4/readme.md
vendored
Normal file
3
cmd/evm/testdata/4/readme.md
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
These files examplify a transition where a transaction (excuted on block 5) requests
|
||||||
|
the blockhash for block `4`, but where the hash for that block is missing.
|
||||||
|
It's expected that executing these should cause `exit` with errorcode `4`.
|
14
cmd/evm/testdata/4/txs.json
vendored
Normal file
14
cmd/evm/testdata/4/txs.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"input" : "0x",
|
||||||
|
"gas" : "0x5f5e100",
|
||||||
|
"gasPrice" : "0x1",
|
||||||
|
"nonce" : "0x0",
|
||||||
|
"to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
|
||||||
|
"value" : "0x186a0",
|
||||||
|
"v" : "0x1b",
|
||||||
|
"r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b",
|
||||||
|
"s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28",
|
||||||
|
"hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81"
|
||||||
|
}
|
||||||
|
]
|
1
cmd/evm/testdata/5/alloc.json
vendored
Normal file
1
cmd/evm/testdata/5/alloc.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
11
cmd/evm/testdata/5/env.json
vendored
Normal file
11
cmd/evm/testdata/5/env.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"currentDifficulty": "0x20000",
|
||||||
|
"currentGasLimit": "0x750a163df65e8a",
|
||||||
|
"currentNumber": "1",
|
||||||
|
"currentTimestamp": "1000",
|
||||||
|
"ommers": [
|
||||||
|
{"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
|
||||||
|
{"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" }
|
||||||
|
]
|
||||||
|
}
|
1
cmd/evm/testdata/5/readme.md
vendored
Normal file
1
cmd/evm/testdata/5/readme.md
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
These files examplify a transition where there are no transcations, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2).
|
1
cmd/evm/testdata/5/txs.json
vendored
Normal file
1
cmd/evm/testdata/5/txs.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
12
cmd/evm/testdata/7/alloc.json
vendored
Normal file
12
cmd/evm/testdata/7/alloc.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||||
|
"balance": "0x5ffd4878be161d74",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0xac",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{
|
||||||
|
"balance": "0xfeedbead",
|
||||||
|
"nonce" : "0x00"
|
||||||
|
}
|
||||||
|
}
|
7
cmd/evm/testdata/7/env.json
vendored
Normal file
7
cmd/evm/testdata/7/env.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
||||||
|
"currentDifficulty": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000",
|
||||||
|
"currentGasLimit": "0x750a163df65e8a",
|
||||||
|
"currentNumber": "5",
|
||||||
|
"currentTimestamp": "1000"
|
||||||
|
}
|
7
cmd/evm/testdata/7/readme.md
vendored
Normal file
7
cmd/evm/testdata/7/readme.md
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
This is a test for HomesteadToDao, checking if the
|
||||||
|
DAO-transition works
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
./statet8n --input.alloc=./testdata/7/alloc.json --input.txs=./testdata/7/txs.json --input.env=./testdata/7/env.json --output.alloc=stdout --state.fork=HomesteadToDaoAt5
|
||||||
|
```
|
1
cmd/evm/testdata/7/txs.json
vendored
Normal file
1
cmd/evm/testdata/7/txs.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
191
cmd/evm/transition-test.sh
Normal file
191
cmd/evm/transition-test.sh
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
ticks="\`\`\`"
|
||||||
|
|
||||||
|
function showjson(){
|
||||||
|
echo "\`$1\`:"
|
||||||
|
echo "${ticks}json"
|
||||||
|
cat $1
|
||||||
|
echo ""
|
||||||
|
echo "$ticks"
|
||||||
|
}
|
||||||
|
function demo(){
|
||||||
|
echo "$ticks"
|
||||||
|
echo "$1"
|
||||||
|
echo "$ticks"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
function tick(){
|
||||||
|
echo "$ticks"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
## EVM state transition tool
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
## 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 that has to be supported are
|
||||||
|
$(tick)
|
||||||
|
|
||||||
|
` ./evm t8n -h | grep "trace\|output\|state\."`
|
||||||
|
|
||||||
|
$(tick)
|
||||||
|
|
||||||
|
### 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\`)
|
||||||
|
|
||||||
|
- 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\`.
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
fi
|
||||||
|
cat << EOF
|
||||||
|
## 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"
|
||||||
|
tick;echo "$cmd"; tick
|
||||||
|
$cmd 2>/dev/null
|
||||||
|
echo "Two resulting files:"
|
||||||
|
echo ""
|
||||||
|
showjson alloc.json
|
||||||
|
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"
|
||||||
|
tick;echo "$cmd"; tick
|
||||||
|
output=`$cmd 2>/dev/null`
|
||||||
|
echo "Output:"
|
||||||
|
echo "${ticks}json"
|
||||||
|
echo "$output"
|
||||||
|
echo "$ticks"
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
## 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\`
|
||||||
|
|
||||||
|
To make \`state_t8n\` apply these, the following inputs are required:
|
||||||
|
|
||||||
|
- \`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\`.
|
||||||
|
|
||||||
|
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
|
||||||
|
the tool allows for negative uncle reward (distance > 8)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
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"
|
||||||
|
output=`$cmd 2>/dev/null`
|
||||||
|
echo "Output:"
|
||||||
|
echo "${ticks}json"
|
||||||
|
echo "$output"
|
||||||
|
echo "$ticks"
|
||||||
|
|
||||||
|
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: "
|
||||||
|
cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json"
|
||||||
|
tick;echo "$cmd"; tick
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
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: "
|
||||||
|
cmd="./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace"
|
||||||
|
tick && echo $cmd && tick
|
||||||
|
$cmd 2>&1 >/dev/null
|
||||||
|
cmd="cat trace-0.jsonl | grep BLOCKHASH -C2"
|
||||||
|
tick && echo $cmd && tick
|
||||||
|
echo "$ticks"
|
||||||
|
cat trace-0.jsonl | grep BLOCKHASH -C2
|
||||||
|
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 && tick
|
||||||
|
tick
|
||||||
|
$cmd
|
||||||
|
errc=$?
|
||||||
|
tick
|
||||||
|
echo "Error code: $errc"
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
echo "$ticks"
|
||||||
|
echo "$cmd1 | $cmd2"
|
||||||
|
output=$($cmd1 | $cmd2 )
|
||||||
|
echo $output
|
||||||
|
echo "$ticks"
|
||||||
|
echo "What happened here, is that we first applied two identical transactions, so the second one was rejected. "
|
||||||
|
echo "Then, taking the poststate alloc as the input for the next state, we tried again to include"
|
||||||
|
echo "the same two transactions: this time, both failed due to too low nonce."
|
||||||
|
echo ""
|
||||||
|
echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the"
|
||||||
|
echo "actual blocknumber (exposed to the EVM) would not increase."
|
||||||
|
echo ""
|
@ -85,7 +85,7 @@ SUBCOMMANDS:
|
|||||||
{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||||
{{end}}{{end}}{{if .Flags}}
|
{{end}}{{end}}{{if .Flags}}
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range $.Flags}}{{"\t"}}{{.}}
|
{{range $.Flags}} {{.}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}`
|
{{end}}`
|
||||||
)
|
)
|
||||||
|
@ -27,6 +27,14 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DumpCollector interface which the state trie calls during iteration
|
||||||
|
type DumpCollector interface {
|
||||||
|
// OnRoot is called with the state root
|
||||||
|
OnRoot(common.Hash)
|
||||||
|
// OnAccount is called once for each account in the trie
|
||||||
|
OnAccount(common.Address, DumpAccount)
|
||||||
|
}
|
||||||
|
|
||||||
// DumpAccount represents an account in the state.
|
// DumpAccount represents an account in the state.
|
||||||
type DumpAccount struct {
|
type DumpAccount struct {
|
||||||
Balance string `json:"balance"`
|
Balance string `json:"balance"`
|
||||||
@ -46,9 +54,14 @@ type Dump struct {
|
|||||||
Accounts map[common.Address]DumpAccount `json:"accounts"`
|
Accounts map[common.Address]DumpAccount `json:"accounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively.
|
// OnRoot implements DumpCollector interface
|
||||||
type iterativeDump struct {
|
func (d *Dump) OnRoot(root common.Hash) {
|
||||||
*json.Encoder
|
d.Root = fmt.Sprintf("%x", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnAccount implements DumpCollector interface
|
||||||
|
func (d *Dump) OnAccount(addr common.Address, account DumpAccount) {
|
||||||
|
d.Accounts[addr] = account
|
||||||
}
|
}
|
||||||
|
|
||||||
// IteratorDump is an implementation for iterating over data.
|
// IteratorDump is an implementation for iterating over data.
|
||||||
@ -58,28 +71,23 @@ type IteratorDump struct {
|
|||||||
Next []byte `json:"next,omitempty"` // nil if no more accounts
|
Next []byte `json:"next,omitempty"` // nil if no more accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collector interface which the state trie calls during iteration
|
// OnRoot implements DumpCollector interface
|
||||||
type collector interface {
|
func (d *IteratorDump) OnRoot(root common.Hash) {
|
||||||
onRoot(common.Hash)
|
|
||||||
onAccount(common.Address, DumpAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dump) onRoot(root common.Hash) {
|
|
||||||
d.Root = fmt.Sprintf("%x", root)
|
d.Root = fmt.Sprintf("%x", root)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dump) onAccount(addr common.Address, account DumpAccount) {
|
// OnAccount implements DumpCollector interface
|
||||||
d.Accounts[addr] = account
|
func (d *IteratorDump) OnAccount(addr common.Address, account DumpAccount) {
|
||||||
}
|
|
||||||
func (d *IteratorDump) onRoot(root common.Hash) {
|
|
||||||
d.Root = fmt.Sprintf("%x", root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *IteratorDump) onAccount(addr common.Address, account DumpAccount) {
|
|
||||||
d.Accounts[addr] = account
|
d.Accounts[addr] = account
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) {
|
// iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively.
|
||||||
|
type iterativeDump struct {
|
||||||
|
*json.Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnAccount implements DumpCollector interface
|
||||||
|
func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) {
|
||||||
dumpAccount := &DumpAccount{
|
dumpAccount := &DumpAccount{
|
||||||
Balance: account.Balance,
|
Balance: account.Balance,
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
@ -96,15 +104,16 @@ func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) {
|
|||||||
d.Encode(dumpAccount)
|
d.Encode(dumpAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d iterativeDump) onRoot(root common.Hash) {
|
// OnRoot implements DumpCollector interface
|
||||||
|
func (d iterativeDump) OnRoot(root common.Hash) {
|
||||||
d.Encode(struct {
|
d.Encode(struct {
|
||||||
Root common.Hash `json:"root"`
|
Root common.Hash `json:"root"`
|
||||||
}{root})
|
}{root})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) {
|
func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) {
|
||||||
missingPreimages := 0
|
missingPreimages := 0
|
||||||
c.onRoot(s.trie.Hash())
|
c.OnRoot(s.trie.Hash())
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
it := trie.NewIterator(s.trie.NodeIterator(start))
|
it := trie.NewIterator(s.trie.NodeIterator(start))
|
||||||
@ -145,7 +154,7 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
|
|||||||
account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content)
|
account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.onAccount(addr, account)
|
c.OnAccount(addr, account)
|
||||||
count++
|
count++
|
||||||
if maxResults > 0 && count >= maxResults {
|
if maxResults > 0 && count >= maxResults {
|
||||||
if it.Next() {
|
if it.Next() {
|
||||||
@ -166,7 +175,7 @@ func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages b
|
|||||||
dump := &Dump{
|
dump := &Dump{
|
||||||
Accounts: make(map[common.Address]DumpAccount),
|
Accounts: make(map[common.Address]DumpAccount),
|
||||||
}
|
}
|
||||||
s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
|
s.DumpToCollector(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
|
||||||
return *dump
|
return *dump
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,14 +184,14 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool
|
|||||||
dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages)
|
dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages)
|
||||||
json, err := json.MarshalIndent(dump, "", " ")
|
json, err := json.MarshalIndent(dump, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("dump err", err)
|
fmt.Println("Dump err", err)
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
|
// IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
|
||||||
func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) {
|
func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) {
|
||||||
s.dump(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
|
s.DumpToCollector(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IteratorDump dumps out a batch of accounts starts with the given start key
|
// IteratorDump dumps out a batch of accounts starts with the given start key
|
||||||
@ -190,6 +199,6 @@ func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreima
|
|||||||
iterator := &IteratorDump{
|
iterator := &IteratorDump{
|
||||||
Accounts: make(map[common.Address]DumpAccount),
|
Accounts: make(map[common.Address]DumpAccount),
|
||||||
}
|
}
|
||||||
iterator.Next = s.dump(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults)
|
iterator.Next = s.DumpToCollector(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults)
|
||||||
return *iterator
|
return *iterator
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func TestDump(t *testing.T) {
|
|||||||
s.state.updateStateObject(obj2)
|
s.state.updateStateObject(obj2)
|
||||||
s.state.Commit(false)
|
s.state.Commit(false)
|
||||||
|
|
||||||
// check that dump contains the state objects that are in trie
|
// check that DumpToCollector contains the state objects that are in trie
|
||||||
got := string(s.state.Dump(false, false, true))
|
got := string(s.state.Dump(false, false, true))
|
||||||
want := `{
|
want := `{
|
||||||
"root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
|
"root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
|
||||||
@ -83,7 +83,7 @@ func TestDump(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", got, want)
|
t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,30 +18,44 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var activators = map[int]func(*JumpTable){
|
||||||
|
2200: enable2200,
|
||||||
|
1884: enable1884,
|
||||||
|
1344: enable1344,
|
||||||
|
2315: enable2315,
|
||||||
|
}
|
||||||
|
|
||||||
// EnableEIP enables the given EIP on the config.
|
// EnableEIP enables the given EIP on the config.
|
||||||
// This operation writes in-place, and callers need to ensure that the globally
|
// This operation writes in-place, and callers need to ensure that the globally
|
||||||
// defined jump tables are not polluted.
|
// defined jump tables are not polluted.
|
||||||
func EnableEIP(eipNum int, jt *JumpTable) error {
|
func EnableEIP(eipNum int, jt *JumpTable) error {
|
||||||
switch eipNum {
|
enablerFn, ok := activators[eipNum]
|
||||||
case 2200:
|
if !ok {
|
||||||
enable2200(jt)
|
|
||||||
case 1884:
|
|
||||||
enable1884(jt)
|
|
||||||
case 1344:
|
|
||||||
enable1344(jt)
|
|
||||||
case 2315:
|
|
||||||
enable2315(jt)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("undefined eip %d", eipNum)
|
return fmt.Errorf("undefined eip %d", eipNum)
|
||||||
}
|
}
|
||||||
|
enablerFn(jt)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidEip(eipNum int) bool {
|
||||||
|
_, ok := activators[eipNum]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
func ActivateableEips() []string {
|
||||||
|
var nums []string
|
||||||
|
for k := range activators {
|
||||||
|
nums = append(nums, fmt.Sprintf("%d", k))
|
||||||
|
}
|
||||||
|
sort.Strings(nums)
|
||||||
|
return nums
|
||||||
|
}
|
||||||
|
|
||||||
// enable1884 applies EIP-1884 to the given jump table:
|
// enable1884 applies EIP-1884 to the given jump table:
|
||||||
// - Increase cost of BALANCE to 700
|
// - Increase cost of BALANCE to 700
|
||||||
// - Increase cost of EXTCODEHASH to 700
|
// - Increase cost of EXTCODEHASH to 700
|
||||||
|
@ -19,6 +19,7 @@ package tests
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
@ -154,6 +155,16 @@ var Forks = map[string]*params.ChainConfig{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the set of defined fork names
|
||||||
|
func AvailableForks() []string {
|
||||||
|
var availableForks []string
|
||||||
|
for k := range Forks {
|
||||||
|
availableForks = append(availableForks, k)
|
||||||
|
}
|
||||||
|
sort.Strings(availableForks)
|
||||||
|
return availableForks
|
||||||
|
}
|
||||||
|
|
||||||
// UnsupportedForkError is returned when a test requests a fork that isn't implemented.
|
// UnsupportedForkError is returned when a test requests a fork that isn't implemented.
|
||||||
type UnsupportedForkError struct {
|
type UnsupportedForkError struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -112,11 +112,11 @@ type stTransactionMarshaling struct {
|
|||||||
PrivateKey hexutil.Bytes
|
PrivateKey hexutil.Bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVMConfig takes a fork definition and returns a chain config.
|
// GetChainConfig takes a fork definition and returns a chain config.
|
||||||
// The fork definition can be
|
// The fork definition can be
|
||||||
// - a plain forkname, e.g. `Byzantium`,
|
// - a plain forkname, e.g. `Byzantium`,
|
||||||
// - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`.
|
// - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`.
|
||||||
func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) {
|
func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) {
|
||||||
var (
|
var (
|
||||||
splitForks = strings.Split(forkString, "+")
|
splitForks = strings.Split(forkString, "+")
|
||||||
ok bool
|
ok bool
|
||||||
@ -129,6 +129,9 @@ func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int,
|
|||||||
if eipNum, err := strconv.Atoi(eip); err != nil {
|
if eipNum, err := strconv.Atoi(eip); err != nil {
|
||||||
return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum)
|
return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum)
|
||||||
} else {
|
} else {
|
||||||
|
if !vm.ValidEip(eipNum) {
|
||||||
|
return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum)
|
||||||
|
}
|
||||||
eips = append(eips, eipNum)
|
eips = append(eips, eipNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +169,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bo
|
|||||||
|
|
||||||
// RunNoVerify runs a specific subtest and returns the statedb and post-state root
|
// RunNoVerify runs a specific subtest and returns the statedb and post-state root
|
||||||
func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, common.Hash, error) {
|
func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, common.Hash, error) {
|
||||||
config, eips, err := getVMConfig(subtest.Fork)
|
config, eips, err := GetChainConfig(subtest.Fork)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork}
|
return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user