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:
committed by
GitHub
parent
2dee31930c
commit
5cff9754d7
@ -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 {
|
||||
|
@ -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:
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user