1// Copyright 2015 The go-ethereum Authors
2// This file is part of the go-ethereum library.
3//
4// The go-ethereum library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// The go-ethereum library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public License
15// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16
17package gasprice
18
19import (
20	"context"
21	"math/big"
22	"sort"
23	"sync"
24
25	"github.com/ethereum/go-ethereum/common"
26	"github.com/ethereum/go-ethereum/core"
27	"github.com/ethereum/go-ethereum/core/types"
28	"github.com/ethereum/go-ethereum/event"
29	"github.com/ethereum/go-ethereum/log"
30	"github.com/ethereum/go-ethereum/params"
31	"github.com/ethereum/go-ethereum/rpc"
32	lru "github.com/hashicorp/golang-lru"
33)
34
35const sampleNumber = 3 // Number of transactions sampled in a block
36
37var (
38	DefaultMaxPrice    = big.NewInt(500 * params.GWei)
39	DefaultIgnorePrice = big.NewInt(2 * params.Wei)
40)
41
42type Config struct {
43	Blocks           int
44	Percentile       int
45	MaxHeaderHistory int
46	MaxBlockHistory  int
47	Default          *big.Int `toml:",omitempty"`
48	MaxPrice         *big.Int `toml:",omitempty"`
49	IgnorePrice      *big.Int `toml:",omitempty"`
50}
51
52// OracleBackend includes all necessary background APIs for oracle.
53type OracleBackend interface {
54	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
55	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
56	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
57	PendingBlockAndReceipts() (*types.Block, types.Receipts)
58	ChainConfig() *params.ChainConfig
59	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
60}
61
62// Oracle recommends gas prices based on the content of recent
63// blocks. Suitable for both light and full clients.
64type Oracle struct {
65	backend     OracleBackend
66	lastHead    common.Hash
67	lastPrice   *big.Int
68	maxPrice    *big.Int
69	ignorePrice *big.Int
70	cacheLock   sync.RWMutex
71	fetchLock   sync.Mutex
72
73	checkBlocks, percentile           int
74	maxHeaderHistory, maxBlockHistory int
75	historyCache                      *lru.Cache
76}
77
78// NewOracle returns a new gasprice oracle which can recommend suitable
79// gasprice for newly created transaction.
80func NewOracle(backend OracleBackend, params Config) *Oracle {
81	blocks := params.Blocks
82	if blocks < 1 {
83		blocks = 1
84		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
85	}
86	percent := params.Percentile
87	if percent < 0 {
88		percent = 0
89		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
90	} else if percent > 100 {
91		percent = 100
92		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
93	}
94	maxPrice := params.MaxPrice
95	if maxPrice == nil || maxPrice.Int64() <= 0 {
96		maxPrice = DefaultMaxPrice
97		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
98	}
99	ignorePrice := params.IgnorePrice
100	if ignorePrice == nil || ignorePrice.Int64() <= 0 {
101		ignorePrice = DefaultIgnorePrice
102		log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice)
103	} else if ignorePrice.Int64() > 0 {
104		log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
105	}
106	maxHeaderHistory := params.MaxHeaderHistory
107	if maxHeaderHistory < 1 {
108		maxHeaderHistory = 1
109		log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory)
110	}
111	maxBlockHistory := params.MaxBlockHistory
112	if maxBlockHistory < 1 {
113		maxBlockHistory = 1
114		log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
115	}
116
117	cache, _ := lru.New(2048)
118	headEvent := make(chan core.ChainHeadEvent, 1)
119	backend.SubscribeChainHeadEvent(headEvent)
120	go func() {
121		var lastHead common.Hash
122		for ev := range headEvent {
123			if ev.Block.ParentHash() != lastHead {
124				cache.Purge()
125			}
126			lastHead = ev.Block.Hash()
127		}
128	}()
129
130	return &Oracle{
131		backend:          backend,
132		lastPrice:        params.Default,
133		maxPrice:         maxPrice,
134		ignorePrice:      ignorePrice,
135		checkBlocks:      blocks,
136		percentile:       percent,
137		maxHeaderHistory: maxHeaderHistory,
138		maxBlockHistory:  maxBlockHistory,
139		historyCache:     cache,
140	}
141}
142
143// SuggestTipCap returns a tip cap so that newly created transaction can have a
144// very high chance to be included in the following blocks.
145//
146// Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
147// necessary to add the basefee to the returned number to fall back to the legacy
148// behavior.
149func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
150	head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
151	headHash := head.Hash()
152
153	// If the latest gasprice is still available, return it.
154	oracle.cacheLock.RLock()
155	lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
156	oracle.cacheLock.RUnlock()
157	if headHash == lastHead {
158		return new(big.Int).Set(lastPrice), nil
159	}
160	oracle.fetchLock.Lock()
161	defer oracle.fetchLock.Unlock()
162
163	// Try checking the cache again, maybe the last fetch fetched what we need
164	oracle.cacheLock.RLock()
165	lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
166	oracle.cacheLock.RUnlock()
167	if headHash == lastHead {
168		return new(big.Int).Set(lastPrice), nil
169	}
170	var (
171		sent, exp int
172		number    = head.Number.Uint64()
173		result    = make(chan results, oracle.checkBlocks)
174		quit      = make(chan struct{})
175		results   []*big.Int
176	)
177	for sent < oracle.checkBlocks && number > 0 {
178		go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
179		sent++
180		exp++
181		number--
182	}
183	for exp > 0 {
184		res := <-result
185		if res.err != nil {
186			close(quit)
187			return new(big.Int).Set(lastPrice), res.err
188		}
189		exp--
190		// Nothing returned. There are two special cases here:
191		// - The block is empty
192		// - All the transactions included are sent by the miner itself.
193		// In these cases, use the latest calculated price for sampling.
194		if len(res.values) == 0 {
195			res.values = []*big.Int{lastPrice}
196		}
197		// Besides, in order to collect enough data for sampling, if nothing
198		// meaningful returned, try to query more blocks. But the maximum
199		// is 2*checkBlocks.
200		if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
201			go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
202			sent++
203			exp++
204			number--
205		}
206		results = append(results, res.values...)
207	}
208	price := lastPrice
209	if len(results) > 0 {
210		sort.Sort(bigIntArray(results))
211		price = results[(len(results)-1)*oracle.percentile/100]
212	}
213	if price.Cmp(oracle.maxPrice) > 0 {
214		price = new(big.Int).Set(oracle.maxPrice)
215	}
216	oracle.cacheLock.Lock()
217	oracle.lastHead = headHash
218	oracle.lastPrice = price
219	oracle.cacheLock.Unlock()
220
221	return new(big.Int).Set(price), nil
222}
223
224type results struct {
225	values []*big.Int
226	err    error
227}
228
229type txSorter struct {
230	txs     []*types.Transaction
231	baseFee *big.Int
232}
233
234func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
235	return &txSorter{
236		txs:     txs,
237		baseFee: baseFee,
238	}
239}
240
241func (s *txSorter) Len() int { return len(s.txs) }
242func (s *txSorter) Swap(i, j int) {
243	s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
244}
245func (s *txSorter) Less(i, j int) bool {
246	// It's okay to discard the error because a tx would never be
247	// accepted into a block with an invalid effective tip.
248	tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
249	tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
250	return tip1.Cmp(tip2) < 0
251}
252
253// getBlockPrices calculates the lowest transaction gas price in a given block
254// and sends it to the result channel. If the block is empty or all transactions
255// are sent by the miner itself(it doesn't make any sense to include this kind of
256// transaction prices for sampling), nil gasprice is returned.
257func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
258	block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
259	if block == nil {
260		select {
261		case result <- results{nil, err}:
262		case <-quit:
263		}
264		return
265	}
266	// Sort the transaction by effective tip in ascending sort.
267	txs := make([]*types.Transaction, len(block.Transactions()))
268	copy(txs, block.Transactions())
269	sorter := newSorter(txs, block.BaseFee())
270	sort.Sort(sorter)
271
272	var prices []*big.Int
273	for _, tx := range sorter.txs {
274		tip, _ := tx.EffectiveGasTip(block.BaseFee())
275		if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
276			continue
277		}
278		sender, err := types.Sender(signer, tx)
279		if err == nil && sender != block.Coinbase() {
280			prices = append(prices, tip)
281			if len(prices) >= limit {
282				break
283			}
284		}
285	}
286	select {
287	case result <- results{prices, nil}:
288	case <-quit:
289	}
290}
291
292type bigIntArray []*big.Int
293
294func (s bigIntArray) Len() int           { return len(s) }
295func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
296func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
297