From e376d2fb31a8fa131ab1f6b9f96ac8a41c94e007 Mon Sep 17 00:00:00 2001
From: Martin Holst Swende <martin@swende.se>
Date: Tue, 30 Jun 2020 10:12:51 +0200
Subject: [PATCH] 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>
---
 cmd/evm/README.md                      | 268 ++++++++++++++++++++++++
 cmd/evm/internal/t8ntool/execution.go  | 255 +++++++++++++++++++++++
 cmd/evm/internal/t8ntool/flags.go      |  99 +++++++++
 cmd/evm/internal/t8ntool/gen_stenv.go  |  80 +++++++
 cmd/evm/internal/t8ntool/transition.go | 276 +++++++++++++++++++++++++
 cmd/evm/main.go                        |  29 ++-
 cmd/evm/poststate.json                 |  23 +++
 cmd/evm/testdata/1/alloc.json          |  12 ++
 cmd/evm/testdata/1/env.json            |   7 +
 cmd/evm/testdata/1/txs.json            |  26 +++
 cmd/evm/testdata/2/alloc.json          |  16 ++
 cmd/evm/testdata/2/env.json            |   7 +
 cmd/evm/testdata/2/readme.md           |   1 +
 cmd/evm/testdata/2/txs.json            |  14 ++
 cmd/evm/testdata/3/alloc.json          |  16 ++
 cmd/evm/testdata/3/env.json            |   8 +
 cmd/evm/testdata/3/readme.md           |   2 +
 cmd/evm/testdata/3/txs.json            |  14 ++
 cmd/evm/testdata/4/alloc.json          |  16 ++
 cmd/evm/testdata/4/env.json            |   8 +
 cmd/evm/testdata/4/readme.md           |   3 +
 cmd/evm/testdata/4/txs.json            |  14 ++
 cmd/evm/testdata/5/alloc.json          |   1 +
 cmd/evm/testdata/5/env.json            |  11 +
 cmd/evm/testdata/5/readme.md           |   1 +
 cmd/evm/testdata/5/txs.json            |   1 +
 cmd/evm/testdata/7/alloc.json          |  12 ++
 cmd/evm/testdata/7/env.json            |   7 +
 cmd/evm/testdata/7/readme.md           |   7 +
 cmd/evm/testdata/7/txs.json            |   1 +
 cmd/evm/transition-test.sh             | 191 +++++++++++++++++
 cmd/utils/flags.go                     |  10 +-
 core/state/dump.go                     |  59 +++---
 core/state/state_test.go               |   4 +-
 core/vm/eips.go                        |  34 ++-
 tests/init.go                          |  11 +
 tests/state_test_util.go               |   9 +-
 37 files changed, 1507 insertions(+), 46 deletions(-)
 create mode 100644 cmd/evm/README.md
 create mode 100644 cmd/evm/internal/t8ntool/execution.go
 create mode 100644 cmd/evm/internal/t8ntool/flags.go
 create mode 100644 cmd/evm/internal/t8ntool/gen_stenv.go
 create mode 100644 cmd/evm/internal/t8ntool/transition.go
 create mode 100644 cmd/evm/poststate.json
 create mode 100644 cmd/evm/testdata/1/alloc.json
 create mode 100644 cmd/evm/testdata/1/env.json
 create mode 100644 cmd/evm/testdata/1/txs.json
 create mode 100644 cmd/evm/testdata/2/alloc.json
 create mode 100644 cmd/evm/testdata/2/env.json
 create mode 100644 cmd/evm/testdata/2/readme.md
 create mode 100644 cmd/evm/testdata/2/txs.json
 create mode 100644 cmd/evm/testdata/3/alloc.json
 create mode 100644 cmd/evm/testdata/3/env.json
 create mode 100644 cmd/evm/testdata/3/readme.md
 create mode 100644 cmd/evm/testdata/3/txs.json
 create mode 100644 cmd/evm/testdata/4/alloc.json
 create mode 100644 cmd/evm/testdata/4/env.json
 create mode 100644 cmd/evm/testdata/4/readme.md
 create mode 100644 cmd/evm/testdata/4/txs.json
 create mode 100644 cmd/evm/testdata/5/alloc.json
 create mode 100644 cmd/evm/testdata/5/env.json
 create mode 100644 cmd/evm/testdata/5/readme.md
 create mode 100644 cmd/evm/testdata/5/txs.json
 create mode 100644 cmd/evm/testdata/7/alloc.json
 create mode 100644 cmd/evm/testdata/7/env.json
 create mode 100644 cmd/evm/testdata/7/readme.md
 create mode 100644 cmd/evm/testdata/7/txs.json
 create mode 100644 cmd/evm/transition-test.sh

diff --git a/cmd/evm/README.md b/cmd/evm/README.md
new file mode 100644
index 000000000..418417475
--- /dev/null
+++ b/cmd/evm/README.md
@@ -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.
+
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
new file mode 100644
index 000000000..a4fa971eb
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -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
+}
diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go
new file mode 100644
index 000000000..2e57d7258
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/flags.go
@@ -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,
+	}
+)
diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go
new file mode 100644
index 000000000..ab5951534
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/gen_stenv.go
@@ -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
+}
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
new file mode 100644
index 000000000..a4908d763
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -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
+}
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 72cb1ab85..473020d5f 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -22,6 +22,7 @@ import (
 	"math/big"
 	"os"
 
+	"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
 	"github.com/ethereum/go-ethereum/cmd/utils"
 	"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() {
 	app.Flags = []cli.Flag{
 		BenchFlag,
@@ -156,13 +178,18 @@ func init() {
 		disasmCommand,
 		runCommand,
 		stateTestCommand,
+		stateTransitionCommand,
 	}
 	cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
 }
 
 func main() {
 	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)
-		os.Exit(1)
+		os.Exit(code)
 	}
 }
diff --git a/cmd/evm/poststate.json b/cmd/evm/poststate.json
new file mode 100644
index 000000000..9ee17f18d
--- /dev/null
+++ b/cmd/evm/poststate.json
@@ -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"
+        }
+    }
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/1/alloc.json b/cmd/evm/testdata/1/alloc.json
new file mode 100644
index 000000000..cef1a25ff
--- /dev/null
+++ b/cmd/evm/testdata/1/alloc.json
@@ -0,0 +1,12 @@
+{
+  "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
+    "balance": "0x5ffd4878be161d74",
+    "code": "0x",
+    "nonce": "0xac",
+    "storage": {}
+  },
+  "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{
+    "balance": "0xfeedbead",
+    "nonce" : "0x00"
+  }
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/1/env.json b/cmd/evm/testdata/1/env.json
new file mode 100644
index 000000000..dd60abd20
--- /dev/null
+++ b/cmd/evm/testdata/1/env.json
@@ -0,0 +1,7 @@
+{
+  "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
+  "currentDifficulty": "0x20000",
+  "currentGasLimit": "0x750a163df65e8a",
+  "currentNumber": "1",
+  "currentTimestamp": "1000"
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/1/txs.json b/cmd/evm/testdata/1/txs.json
new file mode 100644
index 000000000..50b31ff31
--- /dev/null
+++ b/cmd/evm/testdata/1/txs.json
@@ -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"
+  }
+]
diff --git a/cmd/evm/testdata/2/alloc.json b/cmd/evm/testdata/2/alloc.json
new file mode 100644
index 000000000..a9720afc9
--- /dev/null
+++ b/cmd/evm/testdata/2/alloc.json
@@ -0,0 +1,16 @@
+{
+  "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
+    "balance" : "0x0de0b6b3a7640000",
+    "code" : "0x6001600053600160006001f0ff00",
+    "nonce" : "0x00",
+    "storage" : {
+    }
+  },
+  "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
+    "balance" : "0x0de0b6b3a7640000",
+    "code" : "0x",
+    "nonce" : "0x00",
+    "storage" : {
+    }
+  }
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/2/env.json b/cmd/evm/testdata/2/env.json
new file mode 100644
index 000000000..ebadd3f06
--- /dev/null
+++ b/cmd/evm/testdata/2/env.json
@@ -0,0 +1,7 @@
+{
+  "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+  "currentDifficulty" : "0x020000",
+  "currentGasLimit" : "0x3b9aca00",
+  "currentNumber" : "0x01",
+  "currentTimestamp" : "0x03e8"
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/2/readme.md b/cmd/evm/testdata/2/readme.md
new file mode 100644
index 000000000..c116f0e79
--- /dev/null
+++ b/cmd/evm/testdata/2/readme.md
@@ -0,0 +1 @@
+These files examplify a selfdestruct to the `0`-address. 
\ No newline at end of file
diff --git a/cmd/evm/testdata/2/txs.json b/cmd/evm/testdata/2/txs.json
new file mode 100644
index 000000000..304445858
--- /dev/null
+++ b/cmd/evm/testdata/2/txs.json
@@ -0,0 +1,14 @@
+[
+  {
+    "input" : "0x",
+    "gas" : "0x5f5e100",
+    "gasPrice" : "0x1",
+    "nonce" : "0x0",
+    "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
+    "value" : "0x186a0",
+    "v" : "0x1b",
+    "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b",
+    "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28",
+    "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81"
+  }
+]
\ No newline at end of file
diff --git a/cmd/evm/testdata/3/alloc.json b/cmd/evm/testdata/3/alloc.json
new file mode 100644
index 000000000..dca318ee5
--- /dev/null
+++ b/cmd/evm/testdata/3/alloc.json
@@ -0,0 +1,16 @@
+{
+  "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
+    "balance" : "0x0de0b6b3a7640000",
+    "code" : "0x600140",
+    "nonce" : "0x00",
+    "storage" : {
+    }
+  },
+  "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
+    "balance" : "0x0de0b6b3a7640000",
+    "code" : "0x",
+    "nonce" : "0x00",
+    "storage" : {
+    }
+  }
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/3/env.json b/cmd/evm/testdata/3/env.json
new file mode 100644
index 000000000..e283eff46
--- /dev/null
+++ b/cmd/evm/testdata/3/env.json
@@ -0,0 +1,8 @@
+{
+  "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+  "currentDifficulty" : "0x020000",
+  "currentGasLimit" : "0x3b9aca00",
+  "currentNumber" : "0x05",
+  "currentTimestamp" : "0x03e8",
+  "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"}
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/3/readme.md b/cmd/evm/testdata/3/readme.md
new file mode 100644
index 000000000..499f03d7a
--- /dev/null
+++ b/cmd/evm/testdata/3/readme.md
@@ -0,0 +1,2 @@
+These files examplify a transition where a transaction (excuted on block 5) requests
+the blockhash for block `1`. 
diff --git a/cmd/evm/testdata/3/txs.json b/cmd/evm/testdata/3/txs.json
new file mode 100644
index 000000000..304445858
--- /dev/null
+++ b/cmd/evm/testdata/3/txs.json
@@ -0,0 +1,14 @@
+[
+  {
+    "input" : "0x",
+    "gas" : "0x5f5e100",
+    "gasPrice" : "0x1",
+    "nonce" : "0x0",
+    "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
+    "value" : "0x186a0",
+    "v" : "0x1b",
+    "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b",
+    "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28",
+    "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81"
+  }
+]
\ No newline at end of file
diff --git a/cmd/evm/testdata/4/alloc.json b/cmd/evm/testdata/4/alloc.json
new file mode 100644
index 000000000..fadf2bdc4
--- /dev/null
+++ b/cmd/evm/testdata/4/alloc.json
@@ -0,0 +1,16 @@
+{
+  "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
+    "balance" : "0x0de0b6b3a7640000",
+    "code" : "0x600340",
+    "nonce" : "0x00",
+    "storage" : {
+    }
+  },
+  "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
+    "balance" : "0x0de0b6b3a7640000",
+    "code" : "0x",
+    "nonce" : "0x00",
+    "storage" : {
+    }
+  }
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/4/env.json b/cmd/evm/testdata/4/env.json
new file mode 100644
index 000000000..e283eff46
--- /dev/null
+++ b/cmd/evm/testdata/4/env.json
@@ -0,0 +1,8 @@
+{
+  "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+  "currentDifficulty" : "0x020000",
+  "currentGasLimit" : "0x3b9aca00",
+  "currentNumber" : "0x05",
+  "currentTimestamp" : "0x03e8",
+  "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"}
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/4/readme.md b/cmd/evm/testdata/4/readme.md
new file mode 100644
index 000000000..08840d37b
--- /dev/null
+++ b/cmd/evm/testdata/4/readme.md
@@ -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`.
diff --git a/cmd/evm/testdata/4/txs.json b/cmd/evm/testdata/4/txs.json
new file mode 100644
index 000000000..304445858
--- /dev/null
+++ b/cmd/evm/testdata/4/txs.json
@@ -0,0 +1,14 @@
+[
+  {
+    "input" : "0x",
+    "gas" : "0x5f5e100",
+    "gasPrice" : "0x1",
+    "nonce" : "0x0",
+    "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
+    "value" : "0x186a0",
+    "v" : "0x1b",
+    "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b",
+    "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28",
+    "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81"
+  }
+]
\ No newline at end of file
diff --git a/cmd/evm/testdata/5/alloc.json b/cmd/evm/testdata/5/alloc.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/cmd/evm/testdata/5/alloc.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/cmd/evm/testdata/5/env.json b/cmd/evm/testdata/5/env.json
new file mode 100644
index 000000000..1085f63e6
--- /dev/null
+++ b/cmd/evm/testdata/5/env.json
@@ -0,0 +1,11 @@
+{
+  "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "currentDifficulty": "0x20000",
+  "currentGasLimit": "0x750a163df65e8a",
+  "currentNumber": "1",
+  "currentTimestamp": "1000",
+  "ommers": [
+    {"delta":  1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
+    {"delta":  2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" }
+  ]
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/5/readme.md b/cmd/evm/testdata/5/readme.md
new file mode 100644
index 000000000..e2b608fac
--- /dev/null
+++ b/cmd/evm/testdata/5/readme.md
@@ -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).
\ No newline at end of file
diff --git a/cmd/evm/testdata/5/txs.json b/cmd/evm/testdata/5/txs.json
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/cmd/evm/testdata/5/txs.json
@@ -0,0 +1 @@
+[]
diff --git a/cmd/evm/testdata/7/alloc.json b/cmd/evm/testdata/7/alloc.json
new file mode 100644
index 000000000..cef1a25ff
--- /dev/null
+++ b/cmd/evm/testdata/7/alloc.json
@@ -0,0 +1,12 @@
+{
+  "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
+    "balance": "0x5ffd4878be161d74",
+    "code": "0x",
+    "nonce": "0xac",
+    "storage": {}
+  },
+  "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{
+    "balance": "0xfeedbead",
+    "nonce" : "0x00"
+  }
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/7/env.json b/cmd/evm/testdata/7/env.json
new file mode 100644
index 000000000..8fd9bc041
--- /dev/null
+++ b/cmd/evm/testdata/7/env.json
@@ -0,0 +1,7 @@
+{
+  "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
+  "currentDifficulty": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000",
+  "currentGasLimit": "0x750a163df65e8a",
+  "currentNumber": "5",
+  "currentTimestamp": "1000"
+}
\ No newline at end of file
diff --git a/cmd/evm/testdata/7/readme.md b/cmd/evm/testdata/7/readme.md
new file mode 100644
index 000000000..c9826e0ba
--- /dev/null
+++ b/cmd/evm/testdata/7/readme.md
@@ -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
+```
\ No newline at end of file
diff --git a/cmd/evm/testdata/7/txs.json b/cmd/evm/testdata/7/txs.json
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/cmd/evm/testdata/7/txs.json
@@ -0,0 +1 @@
+[]
diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh
new file mode 100644
index 000000000..d1400ca57
--- /dev/null
+++ b/cmd/evm/transition-test.sh
@@ -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 ""
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 088f012fe..445f476cf 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -71,8 +71,8 @@ var (
 {{if .cmd.Description}}{{.cmd.Description}}
 {{end}}{{if .cmd.Subcommands}}
 SUBCOMMANDS:
-	{{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
-	{{end}}{{end}}{{if .categorizedFlags}}
+  {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+  {{end}}{{end}}{{if .categorizedFlags}}
 {{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS:
 {{range $categorized.Flags}}{{"\t"}}{{.}}
 {{end}}
@@ -82,10 +82,10 @@ SUBCOMMANDS:
 {{if .Description}}{{.Description}}
 {{end}}{{if .Subcommands}}
 SUBCOMMANDS:
-	{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
-	{{end}}{{end}}{{if .Flags}}
+  {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+  {{end}}{{end}}{{if .Flags}}
 OPTIONS:
-{{range $.Flags}}{{"\t"}}{{.}}
+{{range $.Flags}}   {{.}}
 {{end}}
 {{end}}`
 )
diff --git a/core/state/dump.go b/core/state/dump.go
index 8224e160b..9bb946d14 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -27,6 +27,14 @@ import (
 	"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.
 type DumpAccount struct {
 	Balance   string                 `json:"balance"`
@@ -46,9 +54,14 @@ type Dump struct {
 	Accounts map[common.Address]DumpAccount `json:"accounts"`
 }
 
-// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively.
-type iterativeDump struct {
-	*json.Encoder
+// OnRoot implements DumpCollector interface
+func (d *Dump) OnRoot(root common.Hash) {
+	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.
@@ -58,28 +71,23 @@ type IteratorDump struct {
 	Next     []byte                         `json:"next,omitempty"` // nil if no more accounts
 }
 
-// Collector interface which the state trie calls during iteration
-type collector interface {
-	onRoot(common.Hash)
-	onAccount(common.Address, DumpAccount)
-}
-
-func (d *Dump) onRoot(root common.Hash) {
+// OnRoot implements DumpCollector interface
+func (d *IteratorDump) OnRoot(root common.Hash) {
 	d.Root = fmt.Sprintf("%x", root)
 }
 
-func (d *Dump) onAccount(addr common.Address, account DumpAccount) {
+// OnAccount implements DumpCollector interface
+func (d *IteratorDump) OnAccount(addr common.Address, account DumpAccount) {
 	d.Accounts[addr] = account
 }
-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
+// iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively.
+type iterativeDump struct {
+	*json.Encoder
 }
 
-func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) {
+// OnAccount implements DumpCollector interface
+func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) {
 	dumpAccount := &DumpAccount{
 		Balance:   account.Balance,
 		Nonce:     account.Nonce,
@@ -96,15 +104,16 @@ func (d iterativeDump) onAccount(addr common.Address, account 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 {
 		Root common.Hash `json:"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
-	c.onRoot(s.trie.Hash())
+	c.OnRoot(s.trie.Hash())
 
 	var count int
 	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)
 			}
 		}
-		c.onAccount(addr, account)
+		c.OnAccount(addr, account)
 		count++
 		if maxResults > 0 && count >= maxResults {
 			if it.Next() {
@@ -166,7 +175,7 @@ func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages b
 	dump := &Dump{
 		Accounts: make(map[common.Address]DumpAccount),
 	}
-	s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
+	s.DumpToCollector(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
 	return *dump
 }
 
@@ -175,14 +184,14 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool
 	dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages)
 	json, err := json.MarshalIndent(dump, "", "    ")
 	if err != nil {
-		fmt.Println("dump err", err)
+		fmt.Println("Dump err", err)
 	}
 	return json
 }
 
 // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
 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
@@ -190,6 +199,6 @@ func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreima
 	iterator := &IteratorDump{
 		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
 }
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 41d9b4655..0dc4c0ad6 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -56,7 +56,7 @@ func TestDump(t *testing.T) {
 	s.state.updateStateObject(obj2)
 	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))
 	want := `{
     "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
@@ -83,7 +83,7 @@ func TestDump(t *testing.T) {
     }
 }`
 	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)
 	}
 }
 
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 0f33659ea..142cfdd84 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -18,30 +18,44 @@ package vm
 
 import (
 	"fmt"
+	"sort"
 
 	"github.com/ethereum/go-ethereum/params"
 	"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.
 // This operation writes in-place, and callers need to ensure that the globally
 // defined jump tables are not polluted.
 func EnableEIP(eipNum int, jt *JumpTable) error {
-	switch eipNum {
-	case 2200:
-		enable2200(jt)
-	case 1884:
-		enable1884(jt)
-	case 1344:
-		enable1344(jt)
-	case 2315:
-		enable2315(jt)
-	default:
+	enablerFn, ok := activators[eipNum]
+	if !ok {
 		return fmt.Errorf("undefined eip %d", eipNum)
 	}
+	enablerFn(jt)
 	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:
 // - Increase cost of BALANCE to 700
 // - Increase cost of EXTCODEHASH to 700
diff --git a/tests/init.go b/tests/init.go
index 70382d356..6c30c3537 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -19,6 +19,7 @@ package tests
 import (
 	"fmt"
 	"math/big"
+	"sort"
 
 	"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.
 type UnsupportedForkError struct {
 	Name string
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index a8d6fac51..a999cba47 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -112,11 +112,11 @@ type stTransactionMarshaling struct {
 	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
 // - a plain forkname, e.g. `Byzantium`,
 // - 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 (
 		splitForks            = strings.Split(forkString, "+")
 		ok                    bool
@@ -129,6 +129,9 @@ func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int,
 		if eipNum, err := strconv.Atoi(eip); err != nil {
 			return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum)
 		} else {
+			if !vm.ValidEip(eipNum) {
+				return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", 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
 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 {
 		return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork}
 	}
-- 
GitLab