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"
|
||||
"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)
|
||||
}
|
||||
}
|
||||
|
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 ""
|
@ -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}}`
|
||||
)
|
||||
|
@ -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) {
|
||||
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) {
|
||||
// OnAccount implements DumpCollector interface
|
||||
func (d *IteratorDump) OnAccount(addr common.Address, account DumpAccount) {
|
||||
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{
|
||||
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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user