Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core, eth, internal, les: simplify gasprice oracle #25

Merged
merged 2 commits into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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