Skip to content

Commit

Permalink
core, eth, internal, les: simplify gasprice oracle (#25)
Browse files Browse the repository at this point in the history
* core, eth, internal, les: simplify gasprice oracle

* eth/gasprice: fix typo
  • Loading branch information
rjl493456442 authored and karalabe committed Jun 1, 2021
1 parent 88caa9e commit 23242b7
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 107 deletions.
10 changes: 6 additions & 4 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
} else {
time = parent.Time() + 10 // block time is fixed at 10 seconds
}

header := &types.Header{
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
ParentHash: parent.Hash(),
Expand All @@ -267,11 +266,14 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time,
}

if chain.Config().IsLondon(parent.Number()) {
if chain.Config().IsLondon(header.Number) {
header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header())
parentGasLimit := parent.GasLimit()
if !chain.Config().IsLondon(parent.Number()) {
parentGasLimit = parent.GasLimit() * params.ElasticityMultiplier
}
header.GasLimit = CalcGasLimit1559(parentGasLimit, parentGasLimit)
}

return header
}

Expand Down
6 changes: 1 addition & 5 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,7 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader {
}

func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx, false)
}

func (b *EthAPIBackend) SuggestTip(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx, true)
return b.gpo.SuggestPrice(ctx)
}

func (b *EthAPIBackend) SuggestFeeCap(ctx context.Context) (*big.Int, error) {
Expand Down
97 changes: 44 additions & 53 deletions eth/gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package gasprice

import (
"context"
"fmt"
"math/big"
"sort"
"sync"
Expand All @@ -31,8 +32,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 @@ -105,7 +108,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {

// SuggestPrice returns a gasprice or tip so that newly created transaction
// can have a very high chance to be included in the following blocks.
func (gpo *Oracle) SuggestPrice(ctx context.Context, tip bool) (*big.Int, error) {
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
headHash := head.Hash()

Expand All @@ -131,10 +134,10 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context, tip bool) (*big.Int, error)
number = head.Number.Uint64()
result = make(chan results, gpo.checkBlocks)
quit = make(chan struct{})
txPrices []*big.Int
results []*big.Int
)
for sent < gpo.checkBlocks && number > 0 {
go gpo.getBlockValues(ctx, tip, 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 @@ -156,18 +159,19 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context, tip bool) (*big.Int, error)
// 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.values) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 {
go gpo.getBlockValues(ctx, tip, 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.values...)
results = append(results, res.values...)
}
fmt.Println(results)
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)
Expand All @@ -184,40 +188,35 @@ type results struct {
err error
}

type SortableTxs interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
GasPrice(int) *big.Int
EffectiveTip(int, *big.Int) *big.Int
type txSorter struct {
txs []*types.Transaction
baseFee *big.Int
}

type transactionsByFeeCap []*types.Transaction

func (t transactionsByFeeCap) Len() int { return len(t) }
func (t transactionsByFeeCap) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t transactionsByFeeCap) Less(i, j int) bool { return t[i].FeeCapCmp(t[j]) < 0 }
func (t transactionsByFeeCap) GasPrice(i int) *big.Int { return t[i].GasPrice() }
func (t transactionsByFeeCap) EffectiveTip(i int, b *big.Int) *big.Int { return nil }

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

func (t transactionsByTip) Len() int { return len(t) }
func (t transactionsByTip) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t transactionsByTip) Less(i, j int) bool { return t[i].TipCmp(t[j]) < 0 }
func (t transactionsByTip) GasPrice(i int) *big.Int { return nil }
func (t transactionsByTip) EffectiveTip(i int, b *big.Int) *big.Int {
// It's okay to discard the error because a tx would never be accepted into
// a block with an invalid effective tip.
et, _ := t[i].EffectiveTip(b)
return et
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) getBlockValues(ctx context.Context, tip bool, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, 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 {
Expand All @@ -226,29 +225,21 @@ func (gpo *Oracle) getBlockValues(ctx context.Context, tip bool, signer types.Si
}
return
}
blockTxs := block.Transactions()
txs := make([]*types.Transaction, len(blockTxs))
copy(txs, blockTxs)

if tip {
sort.Sort(transactionsByTip(txs))
} else {
sort.Sort(transactionsByFeeCap(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.TipIntCmp(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() {
if tip {
et, _ := tx.EffectiveTip(block.BaseFee())
prices = append(prices, et)
} else {
prices = append(prices, tx.GasPrice())
}
prices = append(prices, tip)
if len(prices) >= limit {
break
}
Expand Down
64 changes: 27 additions & 37 deletions eth/gasprice/gasprice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (b *testBackend) ChainConfig() *params.ChainConfig {
return b.chain.Config()
}

func newTestBackend(t *testing.T, isEIP1559 bool) *testBackend {
func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend {
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
Expand All @@ -65,27 +65,27 @@ func newTestBackend(t *testing.T, isEIP1559 bool) *testBackend {
}
signer = types.LatestSigner(gspec.Config)
)
if isEIP1559 {
gspec.Config.LondonBlock = common.Big0
if londonBlock != nil {
gspec.Config.LondonBlock = londonBlock
signer = types.LatestSigner(gspec.Config)
}
engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase()
genesis, _ := gspec.Commit(db)

// Generate testing blocks
blocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, db, 32, func(i int, b *core.BlockGen) {
blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, 32, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{1})

var tx *types.Transaction
if isEIP1559 {
if londonBlock != nil && b.Number().Cmp(londonBlock) >= 0 {
txdata := &types.DynamicFeeTx{
ChainID: gspec.Config.ChainID,
Nonce: b.TxNonce(addr),
To: &common.Address{},
Gas: 30000,
FeeCap: big.NewInt(100 * params.GWei),
Tip: big.NewInt(int64(i+1) * params.GWei * 2),
Tip: big.NewInt(int64(i+1) * params.GWei),
Data: []byte{},
}
tx = types.NewTx(txdata)
Expand All @@ -100,7 +100,6 @@ func newTestBackend(t *testing.T, isEIP1559 bool) *testBackend {
}
tx = types.NewTx(txdata)
}

tx, err := types.SignTx(tx, signer, key)
if err != nil {
t.Fatalf("failed to create tx: %v", err)
Expand All @@ -110,7 +109,7 @@ func newTestBackend(t *testing.T, isEIP1559 bool) *testBackend {
// Construct testing chain
diskdb := rawdb.NewMemoryDatabase()
gspec.Commit(diskdb)
chain, err := core.NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
chain, err := core.NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create local chain, %v", err)
}
Expand All @@ -132,36 +131,27 @@ func TestSuggestPrice(t *testing.T) {
Percentile: 60,
Default: big.NewInt(params.GWei),
}
backend := newTestBackend(t, false)
oracle := NewOracle(backend, config)

// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
got, err := oracle.SuggestPrice(context.Background(), false)
if err != nil {
t.Fatalf("Failed to retrieve recommended gas price: %v", err)
}
expect := big.NewInt(params.GWei * int64(30))
if got.Cmp(expect) != 0 {
t.Fatalf("Gas price mismatch, want %d, got %d", expect, got)
}
}

func TestSuggestTip(t *testing.T) {
config := Config{
Blocks: 3,
Percentile: 60,
Default: big.NewInt(params.GWei),
var cases = []struct {
fork *big.Int // London fork number
expect *big.Int // Expected gasprice suggestion
}{
{nil, big.NewInt(params.GWei * int64(30))},
{big.NewInt(0), big.NewInt(params.GWei * int64(30))}, // Fork point in genesis
{big.NewInt(1), big.NewInt(params.GWei * int64(30))}, // Fork point in first block
{big.NewInt(32), big.NewInt(params.GWei * int64(30))}, // Fork point in last block
{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
}
backend := newTestBackend(t, true)
oracle := NewOracle(backend, config)
for _, c := range cases {
backend := newTestBackend(t, c.fork)
oracle := NewOracle(backend, config)

// The gas price sampled is: 62G, 61G, 60G, 59G, 58G, 57G
got, err := oracle.SuggestPrice(context.Background(), false)
if err != nil {
t.Fatalf("Failed to retrieve recommended gas price: %v", err)
}
expect := big.NewInt(params.GWei * int64(60))
if got.Cmp(expect) != 0 {
t.Fatalf("Gas price mismatch, want %d, got %d", expect, got)
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
got, err := oracle.SuggestPrice(context.Background())
if err != nil {
t.Fatalf("Failed to retrieve recommended gas price: %v", err)
}
if got.Cmp(c.expect) != 0 {
t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got)
}
}
}
2 changes: 1 addition & 1 deletion eth/protocols/snap/range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestHashRanges(t *testing.T) {
head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
chunks: 2,
starts: []common.Hash{
common.Hash{},
{},
common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"),
},
ends: []common.Hash{
Expand Down
1 change: 0 additions & 1 deletion internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ type Backend interface {
// General Ethereum API
Downloader() *downloader.Downloader
SuggestPrice(ctx context.Context) (*big.Int, error)
SuggestTip(ctx context.Context) (*big.Int, error)
SuggestFeeCap(ctx context.Context) (*big.Int, error)
ChainDb() ethdb.Database
AccountManager() *accounts.Manager
Expand Down
2 changes: 1 addition & 1 deletion internal/ethapi/transaction_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
// After london, default to 1559 unless gasPrice is set
if b.ChainConfig().IsLondon(b.CurrentBlock().Number()) && args.GasPrice == nil {
if args.Tip == nil {
tip, err := b.SuggestTip(ctx)
tip, err := b.SuggestPrice(ctx)
if err != nil {
return err
}
Expand Down
6 changes: 1 addition & 5 deletions les/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,7 @@ func (b *LesApiBackend) ProtocolVersion() int {
}

func (b *LesApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx, false)
}

func (b *LesApiBackend) SuggestTip(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx, true)
return b.gpo.SuggestPrice(ctx)
}

func (b *LesApiBackend) SuggestFeeCap(ctx context.Context) (*big.Int, error) {
Expand Down

0 comments on commit 23242b7

Please sign in to comment.