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:
committed by
GitHub
parent
dd91c7ce6a
commit
e376d2fb31
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
|
||||
}
|
Reference in New Issue
Block a user