core, eth, internal, les: RPC methods and fields for EIP 1559 (#22964)

* internal/ethapi: add baseFee to RPCMarshalHeader

* internal/ethapi: add FeeCap, Tip and correct GasPrice to EIP-1559 RPCTransaction results

* core,eth,les,internal: add support for tip estimation in gas price oracle

* internal/ethapi,eth/gasprice: don't suggest tip larger than fee cap

* core/types,internal: use correct eip1559 terminology for json marshalling

* eth, internal/ethapi: fix rebase problems

* internal/ethapi: fix rpc name of basefee

* internal/ethapi: address review concerns

* core, eth, internal, les: simplify gasprice oracle (#25)

* core, eth, internal, les: simplify gasprice oracle

* eth/gasprice: fix typo

* internal/ethapi: minor tweak in tx args

* internal/ethapi: calculate basefee for pending block

* internal/ethapi: fix panic

* internal/ethapi, eth/tracers: simplify txargs ToMessage

* internal/ethapi: remove unused param

* core, eth, internal: fix regressions wrt effective gas price in the evm

* eth/gasprice: drop weird debug println

* internal/jsre/deps: hack in 1559 gas conversions into embedded web3

* internal/jsre/deps: hack basFee to decimal conversion

* internal/ethapi: init feecap and tipcap for legacy txs too

* eth, graphql, internal, les: fix gas price suggestion on all combos

* internal/jsre/deps: handle decimal tipcap and feecap

* eth, internal: minor review fixes

* graphql, internal: export max fee cap RPC endpoint

* internal/ethapi: fix crash in transaction_args

* internal/ethapi: minor refactor to make the code safer

Co-authored-by: Ryan Schneider <ryanleeschneider@gmail.com>
Co-authored-by: lightclient@protonmail.com <lightclient@protonmail.com>
Co-authored-by: gary rong <garyrong0905@gmail.com>
Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
Martin Holst Swende
2021-06-02 15:13:10 +02:00
committed by GitHub
parent 2dee31930c
commit 5cff9754d7
19 changed files with 410 additions and 159 deletions

View File

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@ -58,10 +59,25 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
return &PublicEthereumAPI{b}
}
// GasPrice returns a suggestion for a gas price.
// GasPrice returns a suggestion for a gas price for legacy transactions.
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
price, err := s.b.SuggestPrice(ctx)
return (*hexutil.Big)(price), err
tipcap, err := s.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
if head := s.b.CurrentHeader(); head.BaseFee != nil {
tipcap.Add(tipcap, head.BaseFee)
}
return (*hexutil.Big)(tipcap), err
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic transactions.
func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
tipcap, err := s.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
return (*hexutil.Big)(tipcap), err
}
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
@ -105,12 +121,12 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac
"queued": make(map[string]map[string]*RPCTransaction),
}
pending, queue := s.b.TxPoolContent()
curHeader := s.b.CurrentHeader()
// Flatten the pending transactions
for account, txs := range pending {
dump := make(map[string]*RPCTransaction)
for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx)
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["pending"][account.Hex()] = dump
}
@ -118,7 +134,7 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac
for account, txs := range queue {
dump := make(map[string]*RPCTransaction)
for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx)
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["queued"][account.Hex()] = dump
}
@ -829,7 +845,10 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
defer cancel()
// Get a new instance of the EVM.
msg := args.ToMessage(globalGasCap)
msg, err := args.ToMessage(globalGasCap, header.BaseFee)
if err != nil {
return nil, err
}
evm, vmError, err := b.GetEVM(ctx, msg, state, header, nil)
if err != nil {
return nil, err
@ -1088,7 +1107,7 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes {
// RPCMarshalHeader converts the given header to the RPC output .
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
return map[string]interface{}{
result := map[string]interface{}{
"number": (*hexutil.Big)(head.Number),
"hash": head.Hash(),
"parentHash": head.ParentHash,
@ -1107,6 +1126,12 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
"transactionsRoot": head.TxHash,
"receiptsRoot": head.ReceiptHash,
}
if head.BaseFee != nil {
result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
}
return result
}
// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
@ -1173,6 +1198,8 @@ type RPCTransaction struct {
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
@ -1189,7 +1216,7 @@ type RPCTransaction struct {
// newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *RPCTransaction {
// Determine the signer. For replay-protected transactions, use the most permissive
// signer, because we assume that signers are backwards-compatible with old
// transactions. For non-protected transactions, the homestead signer signer is used
@ -1200,7 +1227,6 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
} else {
signer = types.HomesteadSigner{}
}
from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{
@ -1222,17 +1248,36 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index)
}
if tx.Type() != types.LegacyTxType {
switch tx.Type() {
case types.AccessListTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
case types.DynamicFeeTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.FeeCap = (*hexutil.Big)(tx.FeeCap())
result.Tip = (*hexutil.Big)(tx.Tip())
// if the transaction has been mined, compute the effective gas price
if baseFee != nil && blockHash != (common.Hash{}) {
// price = min(tip, feeCap - baseFee) + baseFee
price := math.BigMin(new(big.Int).Add(tx.Tip(), baseFee), tx.FeeCap())
result.GasPrice = (*hexutil.Big)(price)
} else {
result.GasPrice = nil
}
}
return result
}
// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction {
return newRPCTransaction(tx, common.Hash{}, 0, 0)
func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction {
var baseFee *big.Int
if current != nil {
baseFee = misc.CalcBaseFee(config, current)
}
return newRPCTransaction(tx, common.Hash{}, 0, 0, baseFee)
}
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
@ -1241,7 +1286,7 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti
if index >= uint64(len(txs)) {
return nil
}
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index)
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index, b.BaseFee())
}
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
@ -1450,11 +1495,15 @@ func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, has
return nil, err
}
if tx != nil {
return newRPCTransaction(tx, blockHash, blockNumber, index), nil
header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
return newRPCTransaction(tx, blockHash, blockNumber, index, header.BaseFee), nil
}
// No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil {
return newRPCPendingTransaction(tx), nil
return newRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
}
// Transaction unknown, return as such
@ -1705,11 +1754,12 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
accounts[account.Address] = struct{}{}
}
}
curHeader := s.b.CurrentHeader()
transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending {
from, _ := types.Sender(s.signer, tx)
if _, exists := accounts[from]; exists {
transactions = append(transactions, newRPCPendingTransaction(tx))
transactions = append(transactions, newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()))
}
}
return transactions, nil

View File

@ -41,7 +41,7 @@ import (
type Backend interface {
// General Ethereum API
Downloader() *downloader.Downloader
SuggestPrice(ctx context.Context) (*big.Int, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
ChainDb() ethdb.Database
AccountManager() *accounts.Manager
ExtRPCEnabled() bool

View File

@ -20,6 +20,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
@ -37,6 +38,8 @@ type TransactionArgs struct {
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
FeeCap *hexutil.Big `json:"maxFeePerGas"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
@ -72,12 +75,43 @@ func (arg *TransactionArgs) data() []byte {
// setDefaults fills in default values for unspecified tx fields.
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
if args.GasPrice == nil {
price, err := b.SuggestPrice(ctx)
if err != nil {
return err
if args.GasPrice != nil && (args.FeeCap != nil || args.Tip != nil) {
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
// After london, default to 1559 unless gasPrice is set
head := b.CurrentHeader()
if b.ChainConfig().IsLondon(head.Number) && args.GasPrice == nil {
if args.Tip == nil {
tip, err := b.SuggestGasTipCap(ctx)
if err != nil {
return err
}
args.Tip = (*hexutil.Big)(tip)
}
if args.FeeCap == nil {
feeCap := new(big.Int).Add(
(*big.Int)(args.Tip),
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
)
args.FeeCap = (*hexutil.Big)(feeCap)
}
if args.FeeCap.ToInt().Cmp(args.Tip.ToInt()) < 0 {
return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.FeeCap, args.Tip)
}
} else {
if args.FeeCap != nil || args.Tip != nil {
return errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
}
if args.GasPrice == nil {
price, err := b.SuggestGasTipCap(ctx)
if err != nil {
return err
}
if b.ChainConfig().IsLondon(head.Number) {
price.Add(price, head.BaseFee)
}
args.GasPrice = (*hexutil.Big)(price)
}
args.GasPrice = (*hexutil.Big)(price)
}
if args.Value == nil {
args.Value = new(hexutil.Big)
@ -103,6 +137,8 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
From: args.From,
To: args.To,
GasPrice: args.GasPrice,
FeeCap: args.FeeCap,
Tip: args.Tip,
Value: args.Value,
Data: args.Data,
AccessList: args.AccessList,
@ -123,7 +159,11 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
}
// ToMessage converts TransactionArgs to the Message type used by the core evm
func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message {
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (types.Message, error) {
// Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && (args.FeeCap != nil || args.Tip != nil) {
return types.Message{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
// Set sender address or use zero address if none specified.
addr := args.from()
@ -139,9 +179,34 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap
}
gasPrice := new(big.Int)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
var (
gasPrice *big.Int
feeCap *big.Int
tip *big.Int
)
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
gasPrice = new(big.Int)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
feeCap, tip = gasPrice, gasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
feeCap, tip = gasPrice, gasPrice
} else {
feeCap = new(big.Int)
if args.FeeCap != nil {
feeCap = args.FeeCap.ToInt()
}
tip = new(big.Int)
if args.Tip != nil {
tip = args.Tip.ToInt()
}
gasPrice = math.BigMin(new(big.Int).Add(tip, baseFee), feeCap)
}
}
value := new(big.Int)
if args.Value != nil {
@ -152,24 +217,32 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message {
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false)
return msg
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, feeCap, tip, data, accessList, false)
return msg, nil
}
// toTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
func (args *TransactionArgs) toTransaction() *types.Transaction {
var data types.TxData
if args.AccessList == nil {
data = &types.LegacyTx{
To: args.To,
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: args.data(),
switch {
case args.FeeCap != nil:
al := types.AccessList{}
if args.AccessList != nil {
al = *args.AccessList
}
} else {
data = &types.DynamicFeeTx{
To: args.To,
ChainID: (*big.Int)(args.ChainID),
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
FeeCap: (*big.Int)(args.FeeCap),
Tip: (*big.Int)(args.Tip),
Value: (*big.Int)(args.Value),
Data: args.data(),
AccessList: al,
}
case args.AccessList != nil:
data = &types.AccessListTx{
To: args.To,
ChainID: (*big.Int)(args.ChainID),
@ -180,6 +253,15 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
Data: args.data(),
AccessList: *args.AccessList,
}
default:
data = &types.LegacyTx{
To: args.To,
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: args.data(),
}
}
return types.NewTx(data)
}