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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 410 additions and 159 deletions

@ -252,7 +252,6 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
} else { } else {
time = parent.Time() + 10 // block time is fixed at 10 seconds time = parent.Time() + 10 // block time is fixed at 10 seconds
} }
header := &types.Header{ header := &types.Header{
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
ParentHash: parent.Hash(), ParentHash: parent.Hash(),
@ -267,11 +266,14 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
Number: new(big.Int).Add(parent.Number(), common.Big1), Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time, Time: time,
} }
if chain.Config().IsLondon(header.Number) {
if chain.Config().IsLondon(parent.Number()) {
header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header()) 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 return header
} }

@ -84,7 +84,7 @@ type Header struct {
Nonce BlockNonce `json:"nonce"` Nonce BlockNonce `json:"nonce"`
// BaseFee was added by EIP-1559 and is ignored in legacy headers. // BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFee" rlp:"optional"` BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
} }
// field type overrides for gencodec // field type overrides for gencodec

@ -31,6 +31,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
Extra hexutil.Bytes `json:"extraData" gencodec:"required"` Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"` MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"` Nonce BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -49,6 +50,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.Extra = h.Extra enc.Extra = h.Extra
enc.MixDigest = h.MixDigest enc.MixDigest = h.MixDigest
enc.Nonce = h.Nonce enc.Nonce = h.Nonce
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -71,6 +73,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest *common.Hash `json:"mixHash"` MixDigest *common.Hash `json:"mixHash"`
Nonce *BlockNonce `json:"nonce"` Nonce *BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -134,5 +137,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.Nonce != nil { if dec.Nonce != nil {
h.Nonce = *dec.Nonce h.Nonce = *dec.Nonce
} }
if dec.BaseFee != nil {
h.BaseFee = (*big.Int)(dec.BaseFee)
}
return nil return nil
} }

@ -604,12 +604,10 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
accessList: tx.AccessList(), accessList: tx.AccessList(),
checkNonce: true, checkNonce: true,
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.tip, baseFee), msg.feeCap) msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.tip, baseFee), msg.feeCap)
} }
var err error var err error
msg.from, err = Sender(s, tx) msg.from, err = Sender(s, tx)
return msg, err return msg, err

@ -32,8 +32,6 @@ type txJSON struct {
// Common transaction fields: // Common transaction fields:
Nonce *hexutil.Uint64 `json:"nonce"` Nonce *hexutil.Uint64 `json:"nonce"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
FeeCap *hexutil.Big `json:"feeCap"`
Tip *hexutil.Big `json:"tip"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
Gas *hexutil.Uint64 `json:"gas"` Gas *hexutil.Uint64 `json:"gas"`
@ -88,8 +86,8 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.AccessList = &tx.AccessList enc.AccessList = &tx.AccessList
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.Gas = (*hexutil.Uint64)(&tx.Gas) enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.FeeCap = (*hexutil.Big)(tx.FeeCap) enc.MaxFeePerGas = (*hexutil.Big)(tx.FeeCap)
enc.Tip = (*hexutil.Big)(tx.Tip) enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.Tip)
enc.Value = (*hexutil.Big)(tx.Value) enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data) enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.To = t.To() enc.To = t.To()
@ -226,26 +224,14 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'nonce' in transaction") return errors.New("missing required field 'nonce' in transaction")
} }
itx.Nonce = uint64(*dec.Nonce) itx.Nonce = uint64(*dec.Nonce)
switch { if dec.MaxPriorityFeePerGas == nil {
case dec.Tip == nil && dec.MaxPriorityFeePerGas == nil: return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
return errors.New("at least one of 'tip' or 'maxPriorityFeePerGas' must be defined") }
case dec.Tip != nil && dec.MaxPriorityFeePerGas != nil:
return errors.New("only one of 'tip' or 'maxPriorityFeePerGas' may be defined")
case dec.Tip != nil && dec.MaxPriorityFeePerGas == nil:
itx.Tip = (*big.Int)(dec.Tip)
case dec.Tip == nil && dec.MaxPriorityFeePerGas != nil:
itx.Tip = (*big.Int)(dec.MaxPriorityFeePerGas) itx.Tip = (*big.Int)(dec.MaxPriorityFeePerGas)
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
} }
switch {
case dec.FeeCap == nil && dec.MaxFeePerGas == nil:
return errors.New("at least one of 'feeCap' or 'maxFeePerGas' must be defined")
case dec.FeeCap != nil && dec.MaxFeePerGas != nil:
return errors.New("only one of 'feeCap' or 'maxFeePerGas' may be defined")
case dec.FeeCap != nil && dec.MaxFeePerGas == nil:
itx.FeeCap = (*big.Int)(dec.FeeCap)
case dec.FeeCap == nil && dec.MaxFeePerGas != nil:
itx.FeeCap = (*big.Int)(dec.MaxFeePerGas) itx.FeeCap = (*big.Int)(dec.MaxFeePerGas)
}
if dec.Gas == nil { if dec.Gas == nil {
return errors.New("missing required field 'gas' for txdata") return errors.New("missing required field 'gas' for txdata")
} }

@ -275,8 +275,8 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader {
return b.eth.Downloader() return b.eth.Downloader()
} }
func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx) return b.gpo.SuggestTipCap(ctx)
} }
func (b *EthAPIBackend) ChainDb() ethdb.Database { func (b *EthAPIBackend) ChainDb() ethdb.Database {

@ -31,8 +31,10 @@ import (
const sampleNumber = 3 // Number of transactions sampled in a block const sampleNumber = 3 // Number of transactions sampled in a block
var DefaultMaxPrice = big.NewInt(500 * params.GWei) var (
var DefaultIgnorePrice = big.NewInt(2 * params.Wei) DefaultMaxPrice = big.NewInt(500 * params.GWei)
DefaultIgnorePrice = big.NewInt(2 * params.Wei)
)
type Config struct { type Config struct {
Blocks int Blocks int
@ -103,9 +105,13 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
} }
} }
// SuggestPrice returns a gasprice so that newly created transaction can // SuggestTipCap returns a tip cap so that newly created transaction can have a
// have a very high chance to be included in the following blocks. // very high chance to be included in the following blocks.
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { //
// 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) head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
headHash := head.Hash() headHash := head.Hash()
@ -114,7 +120,7 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
lastHead, lastPrice := gpo.lastHead, gpo.lastPrice lastHead, lastPrice := gpo.lastHead, gpo.lastPrice
gpo.cacheLock.RUnlock() gpo.cacheLock.RUnlock()
if headHash == lastHead { if headHash == lastHead {
return lastPrice, nil return new(big.Int).Set(lastPrice), nil
} }
gpo.fetchLock.Lock() gpo.fetchLock.Lock()
defer gpo.fetchLock.Unlock() defer gpo.fetchLock.Unlock()
@ -124,17 +130,17 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
lastHead, lastPrice = gpo.lastHead, gpo.lastPrice lastHead, lastPrice = gpo.lastHead, gpo.lastPrice
gpo.cacheLock.RUnlock() gpo.cacheLock.RUnlock()
if headHash == lastHead { if headHash == lastHead {
return lastPrice, nil return new(big.Int).Set(lastPrice), nil
} }
var ( var (
sent, exp int sent, exp int
number = head.Number.Uint64() number = head.Number.Uint64()
result = make(chan getBlockPricesResult, gpo.checkBlocks) result = make(chan results, gpo.checkBlocks)
quit = make(chan struct{}) quit = make(chan struct{})
txPrices []*big.Int results []*big.Int
) )
for sent < gpo.checkBlocks && number > 0 { 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++ sent++
exp++ exp++
number-- number--
@ -143,31 +149,31 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
res := <-result res := <-result
if res.err != nil { if res.err != nil {
close(quit) close(quit)
return lastPrice, res.err return new(big.Int).Set(lastPrice), res.err
} }
exp-- exp--
// Nothing returned. There are two special cases here: // Nothing returned. There are two special cases here:
// - The block is empty // - The block is empty
// - All the transactions included are sent by the miner itself. // - All the transactions included are sent by the miner itself.
// In these cases, use the latest calculated price for samping. // In these cases, use the latest calculated price for samping.
if len(res.prices) == 0 { if len(res.values) == 0 {
res.prices = []*big.Int{lastPrice} res.values = []*big.Int{lastPrice}
} }
// Besides, in order to collect enough data for sampling, if nothing // Besides, in order to collect enough data for sampling, if nothing
// meaningful returned, try to query more blocks. But the maximum // meaningful returned, try to query more blocks. But the maximum
// is 2*checkBlocks. // is 2*checkBlocks.
if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 { if len(res.values) == 1 && len(results)+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) go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
sent++ sent++
exp++ exp++
number-- number--
} }
txPrices = append(txPrices, res.prices...) results = append(results, res.values...)
} }
price := lastPrice price := lastPrice
if len(txPrices) > 0 { if len(results) > 0 {
sort.Sort(bigIntArray(txPrices)) sort.Sort(bigIntArray(results))
price = txPrices[(len(txPrices)-1)*gpo.percentile/100] price = results[(len(results)-1)*gpo.percentile/100]
} }
if price.Cmp(gpo.maxPrice) > 0 { if price.Cmp(gpo.maxPrice) > 0 {
price = new(big.Int).Set(gpo.maxPrice) 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.lastHead = headHash
gpo.lastPrice = price gpo.lastPrice = price
gpo.cacheLock.Unlock() gpo.cacheLock.Unlock()
return price, nil
return new(big.Int).Set(price), nil
} }
type getBlockPricesResult struct { type results struct {
prices []*big.Int values []*big.Int
err error err error
} }
type transactionsByGasPrice []*types.Transaction type txSorter struct {
txs []*types.Transaction
baseFee *big.Int
}
func (t transactionsByGasPrice) Len() int { return len(t) } func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } return &txSorter{
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].FeeCapCmp(t[j]) < 0 } 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 // 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 // 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 // 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. // 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)) block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil { if block == nil {
select { select {
case result <- getBlockPricesResult{nil, err}: case result <- results{nil, err}:
case <-quit: case <-quit:
} }
return return
} }
blockTxs := block.Transactions() // Sort the transaction by effective tip in ascending sort.
txs := make([]*types.Transaction, len(blockTxs)) txs := make([]*types.Transaction, len(block.Transactions()))
copy(txs, blockTxs) copy(txs, block.Transactions())
sort.Sort(transactionsByGasPrice(txs)) sorter := newSorter(txs, block.BaseFee())
sort.Sort(sorter)
var prices []*big.Int var prices []*big.Int
for _, tx := range txs { for _, tx := range sorter.txs {
if ignoreUnder != nil && tx.GasPrice().Cmp(ignoreUnder) == -1 { tip, _ := tx.EffectiveTip(block.BaseFee())
if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
continue continue
} }
sender, err := types.Sender(signer, tx) sender, err := types.Sender(signer, tx)
if err == nil && sender != block.Coinbase() { if err == nil && sender != block.Coinbase() {
prices = append(prices, tx.GasPrice()) prices = append(prices, tip)
if len(prices) >= limit { if len(prices) >= limit {
break break
} }
} }
} }
select { select {
case result <- getBlockPricesResult{prices, nil}: case result <- results{prices, nil}:
case <-quit: case <-quit:
} }
} }

@ -55,7 +55,7 @@ func (b *testBackend) ChainConfig() *params.ChainConfig {
return b.chain.Config() return b.chain.Config()
} }
func newTestBackend(t *testing.T) *testBackend { func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend {
var ( var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey) addr = crypto.PubkeyToAddress(key.PublicKey)
@ -65,14 +65,42 @@ func newTestBackend(t *testing.T) *testBackend {
} }
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
) )
if londonBlock != nil {
gspec.Config.LondonBlock = londonBlock
signer = types.LatestSigner(gspec.Config)
}
engine := ethash.NewFaker() engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
genesis, _ := gspec.Commit(db) genesis, _ := gspec.Commit(db)
// Generate testing blocks // 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}) 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 { if err != nil {
t.Fatalf("failed to create tx: %v", err) t.Fatalf("failed to create tx: %v", err)
} }
@ -81,7 +109,7 @@ func newTestBackend(t *testing.T) *testBackend {
// Construct testing chain // Construct testing chain
diskdb := rawdb.NewMemoryDatabase() diskdb := rawdb.NewMemoryDatabase()
gspec.Commit(diskdb) 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 { if err != nil {
t.Fatalf("Failed to create local chain, %v", err) 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) return b.chain.GetBlockByNumber(number)
} }
func TestSuggestPrice(t *testing.T) { func TestSuggestTipCap(t *testing.T) {
config := Config{ config := Config{
Blocks: 3, Blocks: 3,
Percentile: 60, Percentile: 60,
Default: big.NewInt(params.GWei), Default: big.NewInt(params.GWei),
} }
backend := newTestBackend(t) 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
}
for _, c := range cases {
backend := newTestBackend(t, c.fork)
oracle := NewOracle(backend, config) oracle := NewOracle(backend, config)
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
got, err := oracle.SuggestPrice(context.Background()) got, err := oracle.SuggestTipCap(context.Background())
if err != nil { if err != nil {
t.Fatalf("Failed to retrieve recommended gas price: %v", err) t.Fatalf("Failed to retrieve recommended gas price: %v", err)
} }
expect := big.NewInt(params.GWei * int64(30)) if got.Cmp(c.expect) != 0 {
if got.Cmp(expect) != 0 { t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got)
t.Fatalf("Gas price mismatch, want %d, got %d", expect, got) }
} }
} }

@ -53,7 +53,7 @@ func TestHashRanges(t *testing.T) {
head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"), head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
chunks: 2, chunks: 2,
starts: []common.Hash{ starts: []common.Hash{
common.Hash{}, {},
common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"), common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"),
}, },
ends: []common.Hash{ ends: []common.Hash{

@ -762,7 +762,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
} }
} }
// Execute the trace // 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) vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
var traceConfig *TraceConfig var traceConfig *TraceConfig

@ -1106,8 +1106,22 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria
} }
func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) {
price, err := r.backend.SuggestPrice(ctx) tipcap, err := r.backend.SuggestGasTipCap(ctx)
return hexutil.Big(*price), err if err != nil {
return hexutil.Big{}, err
}
if head := r.backend.CurrentHeader(); head.BaseFee != nil {
tipcap.Add(tipcap, head.BaseFee)
}
return (hexutil.Big)(*tipcap), nil
}
func (r *Resolver) MaxPriorityFeePerGas(ctx context.Context) (hexutil.Big, error) {
tipcap, err := r.backend.SuggestGasTipCap(ctx)
if err != nil {
return hexutil.Big{}, err
}
return (hexutil.Big)(*tipcap), nil
} }
func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) { func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) {

@ -319,6 +319,9 @@ const schema string = `
# GasPrice returns the node's estimate of a gas price sufficient to # GasPrice returns the node's estimate of a gas price sufficient to
# ensure a transaction is mined in a timely fashion. # ensure a transaction is mined in a timely fashion.
gasPrice: BigInt! gasPrice: BigInt!
# MaxPriorityFeePerGas returns the node's estimate of a gas tip sufficient
# to ensure a transaction is mined in a timely fashion.
maxPriorityFeePerGas: BigInt!
# Syncing returns information on the current synchronisation state. # Syncing returns information on the current synchronisation state.
syncing: SyncState syncing: SyncState
# ChainID returns the current chain ID for transaction replay protection. # ChainID returns the current chain ID for transaction replay protection.

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -58,10 +59,25 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
return &PublicEthereumAPI{b} return &PublicEthereumAPI{b}
} }
// GasPrice returns a suggestion for a gas price. // GasPrice returns a suggestion for a gas price for legacy transactions.
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
price, err := s.b.SuggestPrice(ctx) tipcap, err := s.b.SuggestGasTipCap(ctx)
return (*hexutil.Big)(price), err if err != nil {
return nil, err
}
if head := s.b.CurrentHeader(); head.BaseFee != nil {
tipcap.Add(tipcap, head.BaseFee)
}
return (*hexutil.Big)(tipcap), err
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic transactions.
func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
tipcap, err := s.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
return (*hexutil.Big)(tipcap), err
} }
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
@ -105,12 +121,12 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac
"queued": make(map[string]map[string]*RPCTransaction), "queued": make(map[string]map[string]*RPCTransaction),
} }
pending, queue := s.b.TxPoolContent() pending, queue := s.b.TxPoolContent()
curHeader := s.b.CurrentHeader()
// Flatten the pending transactions // Flatten the pending transactions
for account, txs := range pending { for account, txs := range pending {
dump := make(map[string]*RPCTransaction) dump := make(map[string]*RPCTransaction)
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx) dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
} }
content["pending"][account.Hex()] = dump content["pending"][account.Hex()] = dump
} }
@ -118,7 +134,7 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac
for account, txs := range queue { for account, txs := range queue {
dump := make(map[string]*RPCTransaction) dump := make(map[string]*RPCTransaction)
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx) dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
} }
content["queued"][account.Hex()] = dump content["queued"][account.Hex()] = dump
} }
@ -829,7 +845,10 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
defer cancel() defer cancel()
// Get a new instance of the EVM. // Get a new instance of the EVM.
msg := args.ToMessage(globalGasCap) msg, err := args.ToMessage(globalGasCap, header.BaseFee)
if err != nil {
return nil, err
}
evm, vmError, err := b.GetEVM(ctx, msg, state, header, nil) evm, vmError, err := b.GetEVM(ctx, msg, state, header, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1088,7 +1107,7 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes {
// RPCMarshalHeader converts the given header to the RPC output . // RPCMarshalHeader converts the given header to the RPC output .
func RPCMarshalHeader(head *types.Header) map[string]interface{} { func RPCMarshalHeader(head *types.Header) map[string]interface{} {
return map[string]interface{}{ result := map[string]interface{}{
"number": (*hexutil.Big)(head.Number), "number": (*hexutil.Big)(head.Number),
"hash": head.Hash(), "hash": head.Hash(),
"parentHash": head.ParentHash, "parentHash": head.ParentHash,
@ -1107,6 +1126,12 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
"transactionsRoot": head.TxHash, "transactionsRoot": head.TxHash,
"receiptsRoot": head.ReceiptHash, "receiptsRoot": head.ReceiptHash,
} }
if head.BaseFee != nil {
result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
}
return result
} }
// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are // RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
@ -1173,6 +1198,8 @@ type RPCTransaction struct {
From common.Address `json:"from"` From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"` Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"` Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"` Nonce hexutil.Uint64 `json:"nonce"`
@ -1189,7 +1216,7 @@ type RPCTransaction struct {
// newRPCTransaction returns a transaction that will serialize to the RPC // newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available). // representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *RPCTransaction {
// Determine the signer. For replay-protected transactions, use the most permissive // Determine the signer. For replay-protected transactions, use the most permissive
// signer, because we assume that signers are backwards-compatible with old // signer, because we assume that signers are backwards-compatible with old
// transactions. For non-protected transactions, the homestead signer signer is used // transactions. For non-protected transactions, the homestead signer signer is used
@ -1200,7 +1227,6 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
} else { } else {
signer = types.HomesteadSigner{} signer = types.HomesteadSigner{}
} }
from, _ := types.Sender(signer, tx) from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues() v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{ result := &RPCTransaction{
@ -1222,17 +1248,36 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index) result.TransactionIndex = (*hexutil.Uint64)(&index)
} }
if tx.Type() != types.LegacyTxType { switch tx.Type() {
case types.AccessListTxType:
al := tx.AccessList() al := tx.AccessList()
result.Accesses = &al result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId()) result.ChainID = (*hexutil.Big)(tx.ChainId())
case types.DynamicFeeTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.FeeCap = (*hexutil.Big)(tx.FeeCap())
result.Tip = (*hexutil.Big)(tx.Tip())
// if the transaction has been mined, compute the effective gas price
if baseFee != nil && blockHash != (common.Hash{}) {
// price = min(tip, feeCap - baseFee) + baseFee
price := math.BigMin(new(big.Int).Add(tx.Tip(), baseFee), tx.FeeCap())
result.GasPrice = (*hexutil.Big)(price)
} else {
result.GasPrice = nil
}
} }
return result return result
} }
// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation // newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction { func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction {
return newRPCTransaction(tx, common.Hash{}, 0, 0) var baseFee *big.Int
if current != nil {
baseFee = misc.CalcBaseFee(config, current)
}
return newRPCTransaction(tx, common.Hash{}, 0, 0, baseFee)
} }
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. // newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
@ -1241,7 +1286,7 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti
if index >= uint64(len(txs)) { if index >= uint64(len(txs)) {
return nil return nil
} }
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index, b.BaseFee())
} }
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. // newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
@ -1450,11 +1495,15 @@ func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, has
return nil, err return nil, err
} }
if tx != nil { if tx != nil {
return newRPCTransaction(tx, blockHash, blockNumber, index), nil header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
return newRPCTransaction(tx, blockHash, blockNumber, index, header.BaseFee), nil
} }
// No finalized transaction, try to retrieve it from the pool // No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil { if tx := s.b.GetPoolTransaction(hash); tx != nil {
return newRPCPendingTransaction(tx), nil return newRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
} }
// Transaction unknown, return as such // Transaction unknown, return as such
@ -1705,11 +1754,12 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
accounts[account.Address] = struct{}{} accounts[account.Address] = struct{}{}
} }
} }
curHeader := s.b.CurrentHeader()
transactions := make([]*RPCTransaction, 0, len(pending)) transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending { for _, tx := range pending {
from, _ := types.Sender(s.signer, tx) from, _ := types.Sender(s.signer, tx)
if _, exists := accounts[from]; exists { if _, exists := accounts[from]; exists {
transactions = append(transactions, newRPCPendingTransaction(tx)) transactions = append(transactions, newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()))
} }
} }
return transactions, nil return transactions, nil

@ -41,7 +41,7 @@ import (
type Backend interface { type Backend interface {
// General Ethereum API // General Ethereum API
Downloader() *downloader.Downloader Downloader() *downloader.Downloader
SuggestPrice(ctx context.Context) (*big.Int, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
ChainDb() ethdb.Database ChainDb() ethdb.Database
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
ExtRPCEnabled() bool ExtRPCEnabled() bool

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -37,6 +38,8 @@ type TransactionArgs struct {
To *common.Address `json:"to"` To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"` Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"` GasPrice *hexutil.Big `json:"gasPrice"`
FeeCap *hexutil.Big `json:"maxFeePerGas"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"` Nonce *hexutil.Uint64 `json:"nonce"`
@ -72,13 +75,44 @@ func (arg *TransactionArgs) data() []byte {
// setDefaults fills in default values for unspecified tx fields. // setDefaults fills in default values for unspecified tx fields.
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
if args.GasPrice == nil { if args.GasPrice != nil && (args.FeeCap != nil || args.Tip != nil) {
price, err := b.SuggestPrice(ctx) return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
// After london, default to 1559 unless gasPrice is set
head := b.CurrentHeader()
if b.ChainConfig().IsLondon(head.Number) && args.GasPrice == nil {
if args.Tip == nil {
tip, err := b.SuggestGasTipCap(ctx)
if err != nil { if err != nil {
return err return err
} }
args.Tip = (*hexutil.Big)(tip)
}
if args.FeeCap == nil {
feeCap := new(big.Int).Add(
(*big.Int)(args.Tip),
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
)
args.FeeCap = (*hexutil.Big)(feeCap)
}
if args.FeeCap.ToInt().Cmp(args.Tip.ToInt()) < 0 {
return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.FeeCap, args.Tip)
}
} else {
if args.FeeCap != nil || args.Tip != nil {
return errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
}
if args.GasPrice == nil {
price, err := b.SuggestGasTipCap(ctx)
if err != nil {
return err
}
if b.ChainConfig().IsLondon(head.Number) {
price.Add(price, head.BaseFee)
}
args.GasPrice = (*hexutil.Big)(price) args.GasPrice = (*hexutil.Big)(price)
} }
}
if args.Value == nil { if args.Value == nil {
args.Value = new(hexutil.Big) args.Value = new(hexutil.Big)
} }
@ -103,6 +137,8 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
From: args.From, From: args.From,
To: args.To, To: args.To,
GasPrice: args.GasPrice, GasPrice: args.GasPrice,
FeeCap: args.FeeCap,
Tip: args.Tip,
Value: args.Value, Value: args.Value,
Data: args.Data, Data: args.Data,
AccessList: args.AccessList, AccessList: args.AccessList,
@ -123,7 +159,11 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
} }
// ToMessage converts TransactionArgs to the Message type used by the core evm // ToMessage converts TransactionArgs to the Message type used by the core evm
func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message { func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (types.Message, error) {
// Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && (args.FeeCap != nil || args.Tip != nil) {
return types.Message{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
// Set sender address or use zero address if none specified. // Set sender address or use zero address if none specified.
addr := args.from() addr := args.from()
@ -139,10 +179,35 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap gas = globalGasCap
} }
gasPrice := new(big.Int) var (
gasPrice *big.Int
feeCap *big.Int
tip *big.Int
)
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
gasPrice = new(big.Int)
if args.GasPrice != nil { if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt() gasPrice = args.GasPrice.ToInt()
} }
feeCap, tip = gasPrice, gasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
feeCap, tip = gasPrice, gasPrice
} else {
feeCap = new(big.Int)
if args.FeeCap != nil {
feeCap = args.FeeCap.ToInt()
}
tip = new(big.Int)
if args.Tip != nil {
tip = args.Tip.ToInt()
}
gasPrice = math.BigMin(new(big.Int).Add(tip, baseFee), feeCap)
}
}
value := new(big.Int) value := new(big.Int)
if args.Value != nil { if args.Value != nil {
value = args.Value.ToInt() value = args.Value.ToInt()
@ -152,24 +217,32 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message {
if args.AccessList != nil { if args.AccessList != nil {
accessList = *args.AccessList accessList = *args.AccessList
} }
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false) msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, feeCap, tip, data, accessList, false)
return msg return msg, nil
} }
// toTransaction converts the arguments to a transaction. // toTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called. // This assumes that setDefaults has been called.
func (args *TransactionArgs) toTransaction() *types.Transaction { func (args *TransactionArgs) toTransaction() *types.Transaction {
var data types.TxData var data types.TxData
if args.AccessList == nil { switch {
data = &types.LegacyTx{ case args.FeeCap != nil:
al := types.AccessList{}
if args.AccessList != nil {
al = *args.AccessList
}
data = &types.DynamicFeeTx{
To: args.To, To: args.To,
ChainID: (*big.Int)(args.ChainID),
Nonce: uint64(*args.Nonce), Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas), Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice), FeeCap: (*big.Int)(args.FeeCap),
Tip: (*big.Int)(args.Tip),
Value: (*big.Int)(args.Value), Value: (*big.Int)(args.Value),
Data: args.data(), Data: args.data(),
AccessList: al,
} }
} else { case args.AccessList != nil:
data = &types.AccessListTx{ data = &types.AccessListTx{
To: args.To, To: args.To,
ChainID: (*big.Int)(args.ChainID), ChainID: (*big.Int)(args.ChainID),
@ -180,6 +253,15 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
Data: args.data(), Data: args.data(),
AccessList: *args.AccessList, AccessList: *args.AccessList,
} }
default:
data = &types.LegacyTx{
To: args.To,
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: args.data(),
}
} }
return types.NewTx(data) return types.NewTx(data)
} }

File diff suppressed because one or more lines are too long

@ -3759,7 +3759,7 @@ var inputTransactionFormatter = function (options){
options.to = inputAddressFormatter(options.to); options.to = inputAddressFormatter(options.to);
} }
['gasPrice', 'gas', 'value', 'nonce'].filter(function (key) { ['maxFeePerGas', 'maxPriorityFeePerGas', 'gasPrice', 'gas', 'value', 'nonce'].filter(function (key) {
return options[key] !== undefined; return options[key] !== undefined;
}).forEach(function(key){ }).forEach(function(key){
options[key] = utils.fromDecimal(options[key]); options[key] = utils.fromDecimal(options[key]);
@ -3783,6 +3783,12 @@ var outputTransactionFormatter = function (tx){
tx.nonce = utils.toDecimal(tx.nonce); tx.nonce = utils.toDecimal(tx.nonce);
tx.gas = utils.toDecimal(tx.gas); tx.gas = utils.toDecimal(tx.gas);
tx.gasPrice = utils.toBigNumber(tx.gasPrice); tx.gasPrice = utils.toBigNumber(tx.gasPrice);
if(tx.maxFeePerGas !== undefined) {
tx.maxFeePerGas = utils.toBigNumber(tx.maxFeePerGas);
}
if(tx.maxPriorityFeePerGas !== undefined) {
tx.maxPriorityFeePerGas = utils.toBigNumber(tx.maxPriorityFeePerGas);
}
tx.value = utils.toBigNumber(tx.value); tx.value = utils.toBigNumber(tx.value);
return tx; return tx;
}; };
@ -3819,8 +3825,10 @@ var outputTransactionReceiptFormatter = function (receipt){
* @returns {Object} * @returns {Object}
*/ */
var outputBlockFormatter = function(block) { var outputBlockFormatter = function(block) {
// transform to number // transform to number
if (block.baseFeePerGas !== undefined) {
block.baseFeePerGas = utils.toBigNumber(block.baseFeePerGas);
}
block.gasLimit = utils.toDecimal(block.gasLimit); block.gasLimit = utils.toDecimal(block.gasLimit);
block.gasUsed = utils.toDecimal(block.gasUsed); block.gasUsed = utils.toDecimal(block.gasUsed);
block.size = utils.toDecimal(block.size); block.size = utils.toDecimal(block.size);

@ -595,6 +595,11 @@ web3._extend({
return formatted; return formatted;
} }
}), }),
new web3._extend.Property({
name: 'maxPriorityFeePerGas',
getter: 'eth_maxPriorityFeePerGas',
outputFormatter: web3._extend.utils.toBigNumber
}),
] ]
}); });
` `

@ -251,8 +251,8 @@ func (b *LesApiBackend) ProtocolVersion() int {
return b.eth.LesVersion() + 10000 return b.eth.LesVersion() + 10000
} }
func (b *LesApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { func (b *LesApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(ctx) return b.gpo.SuggestTipCap(ctx)
} }
func (b *LesApiBackend) ChainDb() ethdb.Database { func (b *LesApiBackend) ChainDb() ethdb.Database {