all: add support for EIP-2718, EIP-2930 transactions (#21502)
This adds support for EIP-2718 typed transactions as well as EIP-2930 access list transactions (tx type 1). These EIPs are scheduled for the Berlin fork. There very few changes to existing APIs in core/types, and several new APIs to deal with access list transactions. In particular, there are two new constructor functions for transactions: types.NewTx and types.SignNewTx. Since the canonical encoding of typed transactions is not RLP-compatible, Transaction now has new methods for encoding and decoding: MarshalBinary and UnmarshalBinary. The existing EIP-155 signer does not support the new transaction types. All code dealing with transaction signatures should be updated to use the newer EIP-2930 signer. To make this easier for future updates, we have added new constructor functions for types.Signer: types.LatestSigner and types.LatestSignerForChainID. This change also adds support for the YoloV3 testnet. Co-authored-by: Martin Holst Swende <martin@swende.se> Co-authored-by: Felix Lange <fjl@twurst.com> Co-authored-by: Ryan Schneider <ryanleeschneider@gmail.com>
This commit is contained in:
@ -17,6 +17,7 @@
|
||||
package t8ntool
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -25,12 +26,15 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"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/crypto"
|
||||
"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"
|
||||
)
|
||||
@ -64,9 +68,9 @@ func (n *NumberedError) Code() int {
|
||||
}
|
||||
|
||||
type input struct {
|
||||
Alloc core.GenesisAlloc `json:"alloc,omitempty"`
|
||||
Env *stEnv `json:"env,omitempty"`
|
||||
Txs types.Transactions `json:"txs,omitempty"`
|
||||
Alloc core.GenesisAlloc `json:"alloc,omitempty"`
|
||||
Env *stEnv `json:"env,omitempty"`
|
||||
Txs []*txWithKey `json:"txs,omitempty"`
|
||||
}
|
||||
|
||||
func Main(ctx *cli.Context) error {
|
||||
@ -135,7 +139,7 @@ func Main(ctx *cli.Context) error {
|
||||
txStr = ctx.String(InputTxsFlag.Name)
|
||||
inputData = &input{}
|
||||
)
|
||||
|
||||
// Figure out the prestate alloc
|
||||
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
decoder.Decode(inputData)
|
||||
@ -151,7 +155,9 @@ func Main(ctx *cli.Context) error {
|
||||
return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err))
|
||||
}
|
||||
}
|
||||
prestate.Pre = inputData.Alloc
|
||||
|
||||
// Set the block environment
|
||||
if envStr != stdinSelector {
|
||||
inFile, err := os.Open(envStr)
|
||||
if err != nil {
|
||||
@ -165,26 +171,8 @@ func Main(ctx *cli.Context) error {
|
||||
}
|
||||
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),
|
||||
@ -200,19 +188,107 @@ func Main(ctx *cli.Context) error {
|
||||
// Set the chain id
|
||||
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
|
||||
|
||||
var txsWithKeys []*txWithKey
|
||||
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)
|
||||
if err := decoder.Decode(&txsWithKeys); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err))
|
||||
}
|
||||
} else {
|
||||
txsWithKeys = inputData.Txs
|
||||
}
|
||||
// We may have to sign the transactions.
|
||||
signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)))
|
||||
|
||||
if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("Failed signing transactions: %v", err))
|
||||
}
|
||||
|
||||
// Iterate over all the tests, run them and aggregate the results
|
||||
|
||||
// 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
|
||||
}
|
||||
body, _ := rlp.EncodeToBytes(txs)
|
||||
// Dump the excution result
|
||||
//postAlloc := state.DumpGenesisFormat(false, false, false)
|
||||
collector := make(Alloc)
|
||||
state.DumpToCollector(collector, false, false, false, nil, -1)
|
||||
return dispatchOutput(ctx, baseDir, result, collector)
|
||||
return dispatchOutput(ctx, baseDir, result, collector, body)
|
||||
|
||||
}
|
||||
|
||||
// txWithKey is a helper-struct, to allow us to use the types.Transaction along with
|
||||
// a `secretKey`-field, for input
|
||||
type txWithKey struct {
|
||||
key *ecdsa.PrivateKey
|
||||
tx *types.Transaction
|
||||
}
|
||||
|
||||
func (t *txWithKey) UnmarshalJSON(input []byte) error {
|
||||
// Read the secretKey, if present
|
||||
type sKey struct {
|
||||
Key *common.Hash `json:"secretKey"`
|
||||
}
|
||||
var key sKey
|
||||
if err := json.Unmarshal(input, &key); err != nil {
|
||||
return err
|
||||
}
|
||||
if key.Key != nil {
|
||||
k := key.Key.Hex()[2:]
|
||||
if ecdsaKey, err := crypto.HexToECDSA(k); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.key = ecdsaKey
|
||||
}
|
||||
}
|
||||
// Now, read the transaction itself
|
||||
var tx types.Transaction
|
||||
if err := json.Unmarshal(input, &tx); err != nil {
|
||||
return err
|
||||
}
|
||||
t.tx = &tx
|
||||
return nil
|
||||
}
|
||||
|
||||
// signUnsignedTransactions converts the input txs to canonical transactions.
|
||||
//
|
||||
// The transactions can have two forms, either
|
||||
// 1. unsigned or
|
||||
// 2. signed
|
||||
// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set.
|
||||
// If so, we sign it here and now, with the given `secretKey`
|
||||
// If the condition above is not met, then it's considered a signed transaction.
|
||||
//
|
||||
// To manage this, we read the transactions twice, first trying to read the secretKeys,
|
||||
// and secondly to read them with the standard tx json format
|
||||
func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) {
|
||||
var signedTxs []*types.Transaction
|
||||
for i, txWithKey := range txs {
|
||||
tx := txWithKey.tx
|
||||
key := txWithKey.key
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
if key != nil && v.BitLen()+r.BitLen()+s.BitLen() == 0 {
|
||||
// This transaction needs to be signed
|
||||
signed, err := types.SignTx(tx, signer, key)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("Tx %d: failed to sign tx: %v", i, err))
|
||||
}
|
||||
signedTxs = append(signedTxs, signed)
|
||||
} else {
|
||||
// Already signed
|
||||
signedTxs = append(signedTxs, tx)
|
||||
}
|
||||
}
|
||||
return signedTxs, nil
|
||||
}
|
||||
|
||||
type Alloc map[common.Address]core.GenesisAccount
|
||||
|
||||
func (g Alloc) OnRoot(common.Hash) {}
|
||||
@ -241,15 +317,17 @@ func saveFile(baseDir, filename string, data interface{}) error {
|
||||
if err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
|
||||
}
|
||||
if err = ioutil.WriteFile(path.Join(baseDir, filename), b, 0644); err != nil {
|
||||
location := path.Join(baseDir, filename)
|
||||
if err = ioutil.WriteFile(location, b, 0644); err != nil {
|
||||
return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err))
|
||||
}
|
||||
log.Info("Wrote file", "file", location)
|
||||
return nil
|
||||
}
|
||||
|
||||
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||
// files
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc) error {
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error {
|
||||
stdOutObject := make(map[string]interface{})
|
||||
stdErrObject := make(map[string]interface{})
|
||||
dispatch := func(baseDir, fName, name string, obj interface{}) error {
|
||||
@ -258,6 +336,8 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
|
||||
stdOutObject[name] = obj
|
||||
case "stderr":
|
||||
stdErrObject[name] = obj
|
||||
case "":
|
||||
// don't save
|
||||
default: // save to file
|
||||
if err := saveFile(baseDir, fName, obj); err != nil {
|
||||
return err
|
||||
@ -271,6 +351,9 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
|
||||
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stdOutObject) > 0 {
|
||||
b, err := json.MarshalIndent(stdOutObject, "", " ")
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user