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:
@ -143,19 +143,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
vmConfig.Debug = (tracer != nil)
|
||||
statedb.Prepare(tx.Hash(), blockHash, txIndex)
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
|
||||
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
|
||||
if chainConfig.IsYoloV3(vmContext.BlockNumber) {
|
||||
statedb.AddAddressToAccessList(msg.From())
|
||||
if dst := msg.To(); dst != nil {
|
||||
statedb.AddAddressToAccessList(*dst)
|
||||
// If it's a create-tx, the destination will be added inside evm.create
|
||||
}
|
||||
for _, addr := range evm.ActivePrecompiles() {
|
||||
statedb.AddAddressToAccessList(addr)
|
||||
}
|
||||
}
|
||||
snapshot := statedb.Snapshot()
|
||||
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
|
||||
|
||||
// (ret []byte, usedGas uint64, failed bool, err error)
|
||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
||||
if err != nil {
|
||||
@ -169,7 +159,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
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
|
||||
|
||||
// Receipt:
|
||||
{
|
||||
var root []byte
|
||||
if chainConfig.IsByzantium(vmContext.BlockNumber) {
|
||||
@ -178,22 +169,32 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes()
|
||||
}
|
||||
|
||||
receipt := types.NewReceipt(root, msgResult.Failed(), gasUsed)
|
||||
// Create a new receipt for the transaction, storing the intermediate root and
|
||||
// gas used by the tx.
|
||||
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: gasUsed}
|
||||
if msgResult.Failed() {
|
||||
receipt.Status = types.ReceiptStatusFailed
|
||||
} else {
|
||||
receipt.Status = types.ReceiptStatusSuccessful
|
||||
}
|
||||
receipt.TxHash = tx.Hash()
|
||||
receipt.GasUsed = msgResult.UsedGas
|
||||
// if the transaction created a contract, store the creation address in the receipt.
|
||||
|
||||
// If the transaction created a contract, store the creation address in the receipt.
|
||||
if msg.To() == nil {
|
||||
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
|
||||
}
|
||||
// Set the receipt logs and create a bloom for filtering
|
||||
|
||||
// Set the receipt logs and create the bloom filter.
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash())
|
||||
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
||||
// These three are non-consensus fields
|
||||
// These three are non-consensus fields:
|
||||
//receipt.BlockHash
|
||||
//receipt.BlockNumber =
|
||||
//receipt.BlockNumber
|
||||
receipt.TransactionIndex = uint(txIndex)
|
||||
receipts = append(receipts, receipt)
|
||||
}
|
||||
|
||||
txIndex++
|
||||
}
|
||||
statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber))
|
||||
|
@ -47,6 +47,11 @@ var (
|
||||
Usage: "Specifies where output files are placed. Will be created if it does not exist.",
|
||||
Value: "",
|
||||
}
|
||||
OutputBodyFlag = cli.StringFlag{
|
||||
Name: "output.body",
|
||||
Usage: "If set, the RLP of the transactions (block body) will be written to this file.",
|
||||
Value: "",
|
||||
}
|
||||
OutputAllocFlag = cli.StringFlag{
|
||||
Name: "output.alloc",
|
||||
Usage: "Determines where to put the `alloc` of the post-state.\n" +
|
||||
|
@ -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