1// Copyright 2020 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"
22	"math/big"
23	"testing"
24
25	"github.com/ethereum/go-ethereum/common"
26	"github.com/ethereum/go-ethereum/consensus/ethash"
27	"github.com/ethereum/go-ethereum/core"
28	"github.com/ethereum/go-ethereum/core/rawdb"
29	"github.com/ethereum/go-ethereum/core/types"
30	"github.com/ethereum/go-ethereum/core/vm"
31	"github.com/ethereum/go-ethereum/crypto"
32	"github.com/ethereum/go-ethereum/event"
33	"github.com/ethereum/go-ethereum/params"
34	"github.com/ethereum/go-ethereum/rpc"
35)
36
37const testHead = 32
38
39type testBackend struct {
40	chain   *core.BlockChain
41	pending bool // pending block available
42}
43
44func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
45	if number > testHead {
46		return nil, nil
47	}
48	if number == rpc.LatestBlockNumber {
49		number = testHead
50	}
51	if number == rpc.PendingBlockNumber {
52		if b.pending {
53			number = testHead + 1
54		} else {
55			return nil, nil
56		}
57	}
58	return b.chain.GetHeaderByNumber(uint64(number)), nil
59}
60
61func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
62	if number > testHead {
63		return nil, nil
64	}
65	if number == rpc.LatestBlockNumber {
66		number = testHead
67	}
68	if number == rpc.PendingBlockNumber {
69		if b.pending {
70			number = testHead + 1
71		} else {
72			return nil, nil
73		}
74	}
75	return b.chain.GetBlockByNumber(uint64(number)), nil
76}
77
78func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
79	return b.chain.GetReceiptsByHash(hash), nil
80}
81
82func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
83	if b.pending {
84		block := b.chain.GetBlockByNumber(testHead + 1)
85		return block, b.chain.GetReceiptsByHash(block.Hash())
86	}
87	return nil, nil
88}
89
90func (b *testBackend) ChainConfig() *params.ChainConfig {
91	return b.chain.Config()
92}
93
94func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
95	return nil
96}
97
98func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend {
99	var (
100		key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
101		addr   = crypto.PubkeyToAddress(key.PublicKey)
102		config = *params.TestChainConfig // needs copy because it is modified below
103		gspec  = &core.Genesis{
104			Config: &config,
105			Alloc:  core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
106		}
107		signer = types.LatestSigner(gspec.Config)
108	)
109	config.LondonBlock = londonBlock
110	config.ArrowGlacierBlock = londonBlock
111	engine := ethash.NewFaker()
112	db := rawdb.NewMemoryDatabase()
113	genesis, err := gspec.Commit(db)
114	if err != nil {
115		t.Fatal(err)
116	}
117	// Generate testing blocks
118	blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, testHead+1, func(i int, b *core.BlockGen) {
119		b.SetCoinbase(common.Address{1})
120
121		var txdata types.TxData
122		if londonBlock != nil && b.Number().Cmp(londonBlock) >= 0 {
123			txdata = &types.DynamicFeeTx{
124				ChainID:   gspec.Config.ChainID,
125				Nonce:     b.TxNonce(addr),
126				To:        &common.Address{},
127				Gas:       30000,
128				GasFeeCap: big.NewInt(100 * params.GWei),
129				GasTipCap: big.NewInt(int64(i+1) * params.GWei),
130				Data:      []byte{},
131			}
132		} else {
133			txdata = &types.LegacyTx{
134				Nonce:    b.TxNonce(addr),
135				To:       &common.Address{},
136				Gas:      21000,
137				GasPrice: big.NewInt(int64(i+1) * params.GWei),
138				Value:    big.NewInt(100),
139				Data:     []byte{},
140			}
141		}
142		b.AddTx(types.MustSignNewTx(key, signer, txdata))
143	})
144	// Construct testing chain
145	diskdb := rawdb.NewMemoryDatabase()
146	gspec.Commit(diskdb)
147	chain, err := core.NewBlockChain(diskdb, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec.Config, engine, vm.Config{}, nil, nil)
148	if err != nil {
149		t.Fatalf("Failed to create local chain, %v", err)
150	}
151	chain.InsertChain(blocks)
152	return &testBackend{chain: chain, pending: pending}
153}
154
155func (b *testBackend) CurrentHeader() *types.Header {
156	return b.chain.CurrentHeader()
157}
158
159func (b *testBackend) GetBlockByNumber(number uint64) *types.Block {
160	return b.chain.GetBlockByNumber(number)
161}
162
163func TestSuggestTipCap(t *testing.T) {
164	config := Config{
165		Blocks:     3,
166		Percentile: 60,
167		Default:    big.NewInt(params.GWei),
168	}
169	var cases = []struct {
170		fork   *big.Int // London fork number
171		expect *big.Int // Expected gasprice suggestion
172	}{
173		{nil, big.NewInt(params.GWei * int64(30))},
174		{big.NewInt(0), big.NewInt(params.GWei * int64(30))},  // Fork point in genesis
175		{big.NewInt(1), big.NewInt(params.GWei * int64(30))},  // Fork point in first block
176		{big.NewInt(32), big.NewInt(params.GWei * int64(30))}, // Fork point in last block
177		{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
178	}
179	for _, c := range cases {
180		backend := newTestBackend(t, c.fork, false)
181		oracle := NewOracle(backend, config)
182
183		// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
184		got, err := oracle.SuggestTipCap(context.Background())
185		if err != nil {
186			t.Fatalf("Failed to retrieve recommended gas price: %v", err)
187		}
188		if got.Cmp(c.expect) != 0 {
189			t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got)
190		}
191	}
192}
193