Skip to content

Commit

Permalink
core, eth, internal, les: RPC methods and fields for EIP 1559 (ethere…
Browse files Browse the repository at this point in the history
  • Loading branch information
gzliudan committed Aug 25, 2024
1 parent 86185bd commit 0372bc9
Show file tree
Hide file tree
Showing 16 changed files with 532 additions and 196 deletions.
2 changes: 1 addition & 1 deletion core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ type Header struct {
Penalties []byte `json:"penalties" gencodec:"required"`

// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFee" rlp:"optional"`
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
}

// field type overrides for gencodec
Expand Down
8 changes: 8 additions & 0 deletions core/types/gen_header_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,10 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int, balanceFee *big.Int
checkNonce: true,
balanceTokenFee: balanceFee,
}

// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.tip, baseFee), msg.feeCap)
}

if balanceFee != nil {
if number.Cmp(common.BlockNumberGas50x) >= 0 {
msg.gasPrice = common.GasPrice50x
Expand Down
48 changes: 25 additions & 23 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package types

import (
Expand All @@ -16,8 +32,6 @@ type txJSON struct {
// Common transaction fields:
Nonce *hexutil.Uint64 `json:"nonce"`
GasPrice *hexutil.Big `json:"gasPrice"`
FeeCap *hexutil.Big `json:"feeCap"`
Tip *hexutil.Big `json:"tip"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
Gas *hexutil.Uint64 `json:"gas"`
Expand Down Expand Up @@ -72,8 +86,8 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.AccessList = &tx.AccessList
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.FeeCap = (*hexutil.Big)(tx.FeeCap)
enc.Tip = (*hexutil.Big)(tx.Tip)
enc.MaxFeePerGas = (*hexutil.Big)(tx.FeeCap)
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.Tip)
enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.To = t.To()
Expand Down Expand Up @@ -210,26 +224,14 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'nonce' in transaction")
}
itx.Nonce = uint64(*dec.Nonce)
switch {
case dec.Tip == nil && dec.MaxPriorityFeePerGas == nil:
return errors.New("at least one of 'tip' or 'maxPriorityFeePerGas' must be defined")
case dec.Tip != nil && dec.MaxPriorityFeePerGas != nil:
return errors.New("only one of 'tip' or 'maxPriorityFeePerGas' may be defined")
case dec.Tip != nil && dec.MaxPriorityFeePerGas == nil:
itx.Tip = (*big.Int)(dec.Tip)
case dec.Tip == nil && dec.MaxPriorityFeePerGas != nil:
itx.Tip = (*big.Int)(dec.MaxPriorityFeePerGas)
}
switch {
case dec.FeeCap == nil && dec.MaxFeePerGas == nil:
return errors.New("at least one of 'feeCap' or 'maxFeePerGas' must be defined")
case dec.FeeCap != nil && dec.MaxFeePerGas != nil:
return errors.New("only one of 'feeCap' or 'maxFeePerGas' may be defined")
case dec.FeeCap != nil && dec.MaxFeePerGas == nil:
itx.FeeCap = (*big.Int)(dec.FeeCap)
case dec.FeeCap == nil && dec.MaxFeePerGas != nil:
itx.FeeCap = (*big.Int)(dec.MaxFeePerGas)
if dec.MaxPriorityFeePerGas == nil {
return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
}
itx.Tip = (*big.Int)(dec.MaxPriorityFeePerGas)
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
itx.FeeCap = (*big.Int)(dec.MaxFeePerGas)
if dec.Gas == nil {
return errors.New("missing required field 'gas' for txdata")
}
Expand Down
8 changes: 6 additions & 2 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,8 @@ func (b *EthApiBackend) ProtocolVersion() int {
return b.eth.EthVersion()
}

func (b *EthApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx)
func (b *EthApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestTipCap(ctx)
}

func (b *EthApiBackend) ChainDb() ethdb.Database {
Expand Down Expand Up @@ -396,6 +396,10 @@ func (b *EthApiBackend) GetEngine() consensus.Engine {
return b.eth.engine
}

func (b *EthApiBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}

func (b *EthApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
return b.eth.stateAtBlock(block, reexec, base, checkLive)
}
Expand Down
5 changes: 4 additions & 1 deletion eth/api_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,10 @@ func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.Transacti
}
}
// Execute the trace
msg := args.ToMessage(api.eth.ApiBackend, block.Number(), api.eth.ApiBackend.RPCGasCap())
msg, err := args.ToMessage(api.eth.ApiBackend, block.Number(), api.eth.ApiBackend.RPCGasCap(), block.BaseFee())
if err != nil {
return nil, err
}
vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
var traceConfig *TraceConfig
if config != nil {
Expand Down
108 changes: 69 additions & 39 deletions eth/gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ import (

const sampleNumber = 3 // Number of transactions sampled in a block

var DefaultMaxPrice = big.NewInt(500 * params.GWei)
var DefaultIgnorePrice = big.NewInt(2 * params.Wei)
var (
DefaultMaxPrice = big.NewInt(500 * params.GWei)
DefaultIgnorePrice = big.NewInt(2 * params.Wei)
)

type Config struct {
Blocks int
Expand Down Expand Up @@ -103,8 +105,13 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
}
}

// SuggestPrice returns the recommended gas price.
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
// SuggestTipCap returns a tip cap so that newly created transaction can have a
// very high chance to be included in the following blocks.
//
// Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
// necessary to add the basefee to the returned number to fall back to the legacy
// behavior.
func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
headHash := head.Hash()

Expand All @@ -113,7 +120,7 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
lastHead, lastPrice := gpo.lastHead, gpo.lastPrice
gpo.cacheLock.RUnlock()
if headHash == lastHead {
return lastPrice, nil
return new(big.Int).Set(lastPrice), nil
}
gpo.fetchLock.Lock()
defer gpo.fetchLock.Unlock()
Expand All @@ -123,17 +130,17 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
lastHead, lastPrice = gpo.lastHead, gpo.lastPrice
gpo.cacheLock.RUnlock()
if headHash == lastHead {
return lastPrice, nil
return new(big.Int).Set(lastPrice), nil
}
var (
sent, exp int
number = head.Number.Uint64()
result = make(chan getBlockPricesResult, gpo.checkBlocks)
result = make(chan results, gpo.checkBlocks)
quit = make(chan struct{})
txPrices []*big.Int
results []*big.Int
)
for sent < gpo.checkBlocks && number > 0 {
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
sent++
exp++
number--
Expand All @@ -142,93 +149,116 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
res := <-result
if res.err != nil {
close(quit)
return lastPrice, res.err
return new(big.Int).Set(lastPrice), res.err
}
exp--
// Nothing returned. There are two special cases here:
// - The block is empty
// - All the transactions included are sent by the miner itself.
// In these cases, use the latest calculated price for samping.
if len(res.prices) == 0 {
res.prices = []*big.Int{lastPrice}
if len(res.values) == 0 {
res.values = []*big.Int{lastPrice}
}
// Besides, in order to collect enough data for sampling, if nothing
// meaningful returned, try to query more blocks. But the maximum
// is 2*checkBlocks.
if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 {
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
if len(res.values) == 1 && len(results)+1+exp < gpo.checkBlocks*2 && number > 0 {
go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
sent++
exp++
number--
}
txPrices = append(txPrices, res.prices...)
results = append(results, res.values...)
}
price := lastPrice
if len(txPrices) > 0 {
sort.Sort(bigIntArray(txPrices))
price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
if len(results) > 0 {
sort.Sort(bigIntArray(results))
price = results[(len(results)-1)*gpo.percentile/100]
}
if price.Cmp(gpo.maxPrice) > 0 {
price = new(big.Int).Set(gpo.maxPrice)
}

// Check gas price min.
minGasPrice := common.GetMinGasPrice(head.Number)
if price.Cmp(minGasPrice) < 0 {
price = new(big.Int).Set(minGasPrice)
// Check min gas price for non-eip1559 block
if head.BaseFee == nil {
minGasPrice := common.GetMinGasPrice(head.Number)
if price.Cmp(minGasPrice) < 0 {
price = new(big.Int).Set(minGasPrice)
}
}

gpo.cacheLock.Lock()
gpo.lastHead = headHash
gpo.lastPrice = price
gpo.cacheLock.Unlock()
return price, nil

return new(big.Int).Set(price), nil
}

type getBlockPricesResult struct {
prices []*big.Int
type results struct {
values []*big.Int
err error
}

type transactionsByGasPrice []*types.Transaction
type txSorter struct {
txs []*types.Transaction
baseFee *big.Int
}

func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
return &txSorter{
txs: txs,
baseFee: baseFee,
}
}

func (t transactionsByGasPrice) Len() int { return len(t) }
func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].FeeCapCmp(t[j]) < 0 }
func (s *txSorter) Len() int { return len(s.txs) }
func (s *txSorter) Swap(i, j int) {
s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
}
func (s *txSorter) Less(i, j int) bool {
// It's okay to discard the error because a tx would never be
// accepted into a block with an invalid effective tip.
tip1, _ := s.txs[i].EffectiveTip(s.baseFee)
tip2, _ := s.txs[j].EffectiveTip(s.baseFee)
return tip1.Cmp(tip2) < 0
}

// getBlockPrices calculates the lowest transaction gas price in a given block
// and sends it to the result channel. If the block is empty or all transactions
// are sent by the miner itself(it doesn't make any sense to include this kind of
// transaction prices for sampling), nil gasprice is returned.
func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan getBlockPricesResult, quit chan struct{}) {
func (gpo *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil {
select {
case result <- getBlockPricesResult{nil, err}:
case result <- results{nil, err}:
case <-quit:
}
return
}
blockTxs := block.Transactions()
txs := make([]*types.Transaction, len(blockTxs))
copy(txs, blockTxs)
sort.Sort(transactionsByGasPrice(txs))
// Sort the transaction by effective tip in ascending sort.
txs := make([]*types.Transaction, len(block.Transactions()))
copy(txs, block.Transactions())
sorter := newSorter(txs, block.BaseFee())
sort.Sort(sorter)

var prices []*big.Int
for _, tx := range txs {
if ignoreUnder != nil && tx.GasPrice().Cmp(ignoreUnder) == -1 {
for _, tx := range sorter.txs {
tip, _ := tx.EffectiveTip(block.BaseFee())
if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
continue
}
sender, err := types.Sender(signer, tx)
if err == nil && sender != block.Coinbase() {
prices = append(prices, tx.GasPrice())
prices = append(prices, tip)
if len(prices) >= limit {
break
}
}
}
select {
case result <- getBlockPricesResult{prices, nil}:
case result <- results{prices, nil}:
case <-quit:
}
}
Expand Down
Loading

0 comments on commit 0372bc9

Please sign in to comment.