core, eth, internal, les: RPC methods and fields for EIP 1559 (#22964)

* internal/ethapi: add baseFee to RPCMarshalHeader

* internal/ethapi: add FeeCap, Tip and correct GasPrice to EIP-1559 RPCTransaction results

* core,eth,les,internal: add support for tip estimation in gas price oracle

* internal/ethapi,eth/gasprice: don't suggest tip larger than fee cap

* core/types,internal: use correct eip1559 terminology for json marshalling

* eth, internal/ethapi: fix rebase problems

* internal/ethapi: fix rpc name of basefee

* internal/ethapi: address review concerns

* core, eth, internal, les: simplify gasprice oracle (#25)

* core, eth, internal, les: simplify gasprice oracle

* eth/gasprice: fix typo

* internal/ethapi: minor tweak in tx args

* internal/ethapi: calculate basefee for pending block

* internal/ethapi: fix panic

* internal/ethapi, eth/tracers: simplify txargs ToMessage

* internal/ethapi: remove unused param

* core, eth, internal: fix regressions wrt effective gas price in the evm

* eth/gasprice: drop weird debug println

* internal/jsre/deps: hack in 1559 gas conversions into embedded web3

* internal/jsre/deps: hack basFee to decimal conversion

* internal/ethapi: init feecap and tipcap for legacy txs too

* eth, graphql, internal, les: fix gas price suggestion on all combos

* internal/jsre/deps: handle decimal tipcap and feecap

* eth, internal: minor review fixes

* graphql, internal: export max fee cap RPC endpoint

* internal/ethapi: fix crash in transaction_args

* internal/ethapi: minor refactor to make the code safer

Co-authored-by: Ryan Schneider <ryanleeschneider@gmail.com>
Co-authored-by: lightclient@protonmail.com <lightclient@protonmail.com>
Co-authored-by: gary rong <garyrong0905@gmail.com>
Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
Martin Holst Swende
2021-06-02 15:13:10 +02:00
committed by GitHub
parent 2dee31930c
commit 5cff9754d7
19 changed files with 410 additions and 159 deletions

View File

@ -275,8 +275,8 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader {
return b.eth.Downloader()
}
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 {

View File

@ -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
@ -103,9 +105,13 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
}
}
// SuggestPrice returns a gasprice 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) (*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()
@ -114,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()
@ -124,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--
@ -143,31 +149,31 @@ 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)
@ -176,53 +182,74 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
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 (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 newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
return &txSorter{
txs: txs,
baseFee: baseFee,
}
}
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:
}
}

View File

@ -55,7 +55,7 @@ func (b *testBackend) ChainConfig() *params.ChainConfig {
return b.chain.Config()
}
func newTestBackend(t *testing.T) *testBackend {
func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend {
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
@ -65,14 +65,42 @@ func newTestBackend(t *testing.T) *testBackend {
}
signer = types.LatestSigner(gspec.Config)
)
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})
tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr), common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key)
var tx *types.Transaction
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),
Data: []byte{},
}
tx = types.NewTx(txdata)
} else {
txdata := &types.LegacyTx{
Nonce: b.TxNonce(addr),
To: &common.Address{},
Gas: 21000,
GasPrice: big.NewInt(int64(i+1) * params.GWei),
Value: big.NewInt(100),
Data: []byte{},
}
tx = types.NewTx(txdata)
}
tx, err := types.SignTx(tx, signer, key)
if err != nil {
t.Fatalf("failed to create tx: %v", err)
}
@ -81,7 +109,7 @@ func newTestBackend(t *testing.T) *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)
}
@ -97,22 +125,33 @@ func (b *testBackend) GetBlockByNumber(number uint64) *types.Block {
return b.chain.GetBlockByNumber(number)
}
func TestSuggestPrice(t *testing.T) {
func TestSuggestTipCap(t *testing.T) {
config := Config{
Blocks: 3,
Percentile: 60,
Default: big.NewInt(params.GWei),
}
backend := newTestBackend(t)
oracle := NewOracle(backend, config)
// 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)
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
}
expect := big.NewInt(params.GWei * int64(30))
if got.Cmp(expect) != 0 {
t.Fatalf("Gas price mismatch, want %d, got %d", expect, got)
for _, c := range cases {
backend := newTestBackend(t, c.fork)
oracle := NewOracle(backend, config)
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
got, err := oracle.SuggestTipCap(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)
}
}
}

View File

@ -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{

View File

@ -762,7 +762,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
}
}
// Execute the trace
msg := args.ToMessage(api.backend.RPCGasCap())
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
if err != nil {
return nil, err
}
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
var traceConfig *TraceConfig