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:
committed by
GitHub
parent
2dee31930c
commit
5cff9754d7
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user