cmd/evm: transaction validation tool (#23494)
* cmd/evm: transaction validation tool * cmd/evm: add hash to t9n tool * cmd/evm: lint nits * cmd/evm: nitpicks
This commit is contained in:
parent
578bc8164d
commit
babe9b993e
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2021 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type result struct {
|
||||
Error error
|
||||
Address common.Address
|
||||
Hash common.Hash
|
||||
}
|
||||
|
||||
// MarshalJSON marshals as JSON with a hash.
|
||||
func (r *result) MarshalJSON() ([]byte, error) {
|
||||
type xx struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
Address *common.Address `json:"address,omitempty"`
|
||||
Hash *common.Hash `json:"hash,omitempty"`
|
||||
}
|
||||
var out xx
|
||||
if r.Error != nil {
|
||||
out.Error = r.Error.Error()
|
||||
}
|
||||
if r.Address != (common.Address{}) {
|
||||
out.Address = &r.Address
|
||||
}
|
||||
if r.Hash != (common.Hash{}) {
|
||||
out.Hash = &r.Hash
|
||||
}
|
||||
return json.Marshal(out)
|
||||
}
|
||||
|
||||
func Transaction(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
|
||||
)
|
||||
// We need to load the transactions. May be either in stdin input or in files.
|
||||
// Check if anything needs to be read from stdin
|
||||
var (
|
||||
txStr = ctx.String(InputTxsFlag.Name)
|
||||
inputData = &input{}
|
||||
chainConfig *params.ChainConfig
|
||||
)
|
||||
// Construct the chainconfig
|
||||
if cConf, _, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {
|
||||
return NewError(ErrorVMConfig, fmt.Errorf("failed constructing chain configuration: %v", err))
|
||||
} else {
|
||||
chainConfig = cConf
|
||||
}
|
||||
// Set the chain id
|
||||
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
|
||||
var body hexutil.Bytes
|
||||
if txStr == stdinSelector {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
if err := decoder.Decode(inputData); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
|
||||
}
|
||||
// Decode the body of already signed transactions
|
||||
body = common.FromHex(inputData.TxRlp)
|
||||
} else {
|
||||
// Read input from file
|
||||
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)
|
||||
if strings.HasSuffix(txStr, ".rlp") {
|
||||
if err := decoder.Decode(&body); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return NewError(ErrorIO, errors.New("only rlp supported"))
|
||||
}
|
||||
}
|
||||
signer := types.MakeSigner(chainConfig, new(big.Int))
|
||||
// We now have the transactions in 'body', which is supposed to be an
|
||||
// rlp list of transactions
|
||||
it, err := rlp.NewListIterator([]byte(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var results []result
|
||||
for it.Next() {
|
||||
var tx types.Transaction
|
||||
err := rlp.DecodeBytes(it.Value(), &tx)
|
||||
if err != nil {
|
||||
results = append(results, result{Error: err})
|
||||
continue
|
||||
}
|
||||
sender, err := types.Sender(signer, &tx)
|
||||
if err != nil {
|
||||
results = append(results, result{Error: err})
|
||||
continue
|
||||
}
|
||||
results = append(results, result{Address: sender, Hash: tx.Hash()})
|
||||
}
|
||||
out, err := json.MarshalIndent(results, "", " ")
|
||||
fmt.Println(string(out))
|
||||
return err
|
||||
}
|
|
@ -81,7 +81,7 @@ type input struct {
|
|||
TxRlp string `json:"txsRlp,omitempty"`
|
||||
}
|
||||
|
||||
func Main(ctx *cli.Context) error {
|
||||
func Transition(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)))
|
||||
|
|
|
@ -135,7 +135,7 @@ var stateTransitionCommand = cli.Command{
|
|||
Name: "transition",
|
||||
Aliases: []string{"t8n"},
|
||||
Usage: "executes a full state transition",
|
||||
Action: t8ntool.Main,
|
||||
Action: t8ntool.Transition,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.TraceFlag,
|
||||
t8ntool.TraceDisableMemoryFlag,
|
||||
|
@ -154,6 +154,18 @@ var stateTransitionCommand = cli.Command{
|
|||
t8ntool.VerbosityFlag,
|
||||
},
|
||||
}
|
||||
var transactionCommand = cli.Command{
|
||||
Name: "transaction",
|
||||
Aliases: []string{"t9n"},
|
||||
Usage: "performs transaction validation",
|
||||
Action: t8ntool.Transaction,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.InputTxsFlag,
|
||||
t8ntool.ChainIDFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
t8ntool.VerbosityFlag,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
app.Flags = []cli.Flag{
|
||||
|
@ -187,6 +199,7 @@ func init() {
|
|||
runCommand,
|
||||
stateTestCommand,
|
||||
stateTransitionCommand,
|
||||
transactionCommand,
|
||||
}
|
||||
cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@ type t8nOutput struct {
|
|||
}
|
||||
|
||||
func (args *t8nOutput) get() (out []string) {
|
||||
out = append(out, "t8n")
|
||||
if args.body {
|
||||
out = append(out, "--output.body", "stdout")
|
||||
} else {
|
||||
|
@ -173,7 +172,9 @@ func TestT8n(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
|
||||
args := append(tc.output.get(), tc.input.get(tc.base)...)
|
||||
args := []string{"t8n"}
|
||||
args = append(args, tc.output.get()...)
|
||||
args = append(args, tc.input.get(tc.base)...)
|
||||
tt.Run("evm-test", args...)
|
||||
tt.Logf("args: %v\n", strings.Join(args, " "))
|
||||
// Compare the expected output, if provided
|
||||
|
@ -198,6 +199,86 @@ func TestT8n(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type t9nInput struct {
|
||||
inTxs string
|
||||
stFork string
|
||||
}
|
||||
|
||||
func (args *t9nInput) get(base string) []string {
|
||||
var out []string
|
||||
if opt := args.inTxs; opt != "" {
|
||||
out = append(out, "--input.txs")
|
||||
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
||||
}
|
||||
if opt := args.stFork; opt != "" {
|
||||
out = append(out, "--state.fork", opt)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestT9n(t *testing.T) {
|
||||
tt := new(testT8n)
|
||||
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
||||
for i, tc := range []struct {
|
||||
base string
|
||||
input t9nInput
|
||||
expExitCode int
|
||||
expOut string
|
||||
}{
|
||||
{ // London txs on homestead
|
||||
base: "./testdata/15",
|
||||
input: t9nInput{
|
||||
inTxs: "signed_txs.rlp",
|
||||
stFork: "Homestead",
|
||||
},
|
||||
expOut: "exp.json",
|
||||
},
|
||||
{ // London txs on homestead
|
||||
base: "./testdata/15",
|
||||
input: t9nInput{
|
||||
inTxs: "signed_txs.rlp",
|
||||
stFork: "London",
|
||||
},
|
||||
expOut: "exp2.json",
|
||||
},
|
||||
{ // An RLP list (a blockheader really)
|
||||
base: "./testdata/15",
|
||||
input: t9nInput{
|
||||
inTxs: "blockheader.rlp",
|
||||
stFork: "London",
|
||||
},
|
||||
expOut: "exp3.json",
|
||||
},
|
||||
} {
|
||||
|
||||
args := []string{"t9n"}
|
||||
args = append(args, tc.input.get(tc.base)...)
|
||||
|
||||
tt.Run("evm-test", args...)
|
||||
tt.Logf("args:\n go run . %v\n", strings.Join(args, " "))
|
||||
// Compare the expected output, if provided
|
||||
if tc.expOut != "" {
|
||||
want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut))
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: could not read expected output: %v", i, err)
|
||||
}
|
||||
have := tt.Output()
|
||||
ok, err := cmpJson(have, want)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Logf(string(have))
|
||||
t.Fatalf("test %d, json parsing failed: %v", i, err)
|
||||
case !ok:
|
||||
t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))
|
||||
}
|
||||
}
|
||||
tt.WaitExit()
|
||||
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
|
||||
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cmpJson compares the JSON in two byte slices.
|
||||
func cmpJson(a, b []byte) (bool, error) {
|
||||
var j, j2 interface{}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"0xf901f0a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b0101020383010203a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
{
|
||||
"address": "0xd02d72e067e77158444ef2020ff2d325f929b363",
|
||||
"hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476"
|
||||
},
|
||||
{
|
||||
"address": "0xd02d72e067e77158444ef2020ff2d325f929b363",
|
||||
"hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,47 @@
|
|||
[
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "rlp: expected List"
|
||||
},
|
||||
{
|
||||
"error": "rlp: expected List"
|
||||
},
|
||||
{
|
||||
"error": "rlp: expected List"
|
||||
},
|
||||
{
|
||||
"error": "rlp: expected List"
|
||||
},
|
||||
{
|
||||
"error": "rlp: expected List"
|
||||
},
|
||||
{
|
||||
"error": "rlp: expected input list for types.AccessListTx"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
},
|
||||
{
|
||||
"error": "transaction type not supported"
|
||||
}
|
||||
]
|
|
@ -0,0 +1 @@
|
|||
"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9"
|
Loading…
Reference in New Issue