Add Ethereum type EIP1559 fee estimate from infura

This commit is contained in:
Martin Boehm
2024-11-17 11:46:24 +01:00
parent a3b0a05b14
commit a2ba8c4b09
10 changed files with 277 additions and 37 deletions

View File

@@ -571,13 +571,21 @@ type AvailableVsCurrencies struct {
type Eip1559Fee struct {
MaxFeePerGas *Amount `json:"maxFeePerGas"`
MaxPriorityFeePerGas *Amount `json:"maxPriorityFeePerGas"`
MinWaitTimeEstimate int `json:"minWaitTimeEstimate,omitempty"`
MaxWaitTimeEstimate int `json:"maxWaitTimeEstimate,omitempty"`
}
// Eip1559Fees
type Eip1559Fees struct {
BaseFeePerGas *Amount `json:"baseFeePerGas,omitempty"`
Low *Eip1559Fee `json:"low,omitempty"`
Medium *Eip1559Fee `json:"medium,omitempty"`
High *Eip1559Fee `json:"high,omitempty"`
Instant *Eip1559Fee `json:"instant,omitempty"`
BaseFeePerGas *Amount `json:"baseFeePerGas,omitempty"`
Low *Eip1559Fee `json:"low,omitempty"`
Medium *Eip1559Fee `json:"medium,omitempty"`
High *Eip1559Fee `json:"high,omitempty"`
Instant *Eip1559Fee `json:"instant,omitempty"`
NetworkCongestion float64 `json:"networkCongestion,omitempty"`
LatestPriorityFeeRange []*Amount `json:"latestPriorityFeeRange,omitempty"`
HistoricalPriorityFeeRange []*Amount `json:"historicalPriorityFeeRange,omitempty"`
HistoricalBaseFeeRange []*Amount `json:"historicalBaseFeeRange,omitempty"`
PriorityFeeTrend string `json:"priorityFeeTrend,omitempty"`
BaseFeeTrend string `json:"baseFeeTrend,omitempty"`
}

View File

@@ -17,3 +17,9 @@ type alternativeFeeProvider struct {
type alternativeFeeProviderInterface interface {
GetEip1559Fees() (*bchain.Eip1559Fees, error)
}
func (p *alternativeFeeProvider) GetEip1559Fees() (*bchain.Eip1559Fees, error) {
p.mux.Lock()
defer p.mux.Unlock()
return p.eip1559Fees, nil
}

View File

@@ -110,6 +110,23 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
s.Timeout = time.Duration(c.RPCTimeout) * time.Second
s.PushHandler = pushHandler
if s.ChainConfig.AlternativeEstimateFee == "1inch" {
if s.alternativeFeeProvider, err = NewOneInchFeesProvider(s, s.ChainConfig.AlternativeEstimateFeeParams); err != nil {
glog.Error("New1InchFeesProvider error ", err, " Reverting to default estimateFee functionality")
// disable AlternativeEstimateFee logic
s.alternativeFeeProvider = nil
}
} else if s.ChainConfig.AlternativeEstimateFee == "infura" {
if s.alternativeFeeProvider, err = NewInfuraFeesProvider(s, s.ChainConfig.AlternativeEstimateFeeParams); err != nil {
glog.Error("NewInfuraFeesProvider error ", err, " Reverting to default estimateFee functionality")
// disable AlternativeEstimateFee logic
s.alternativeFeeProvider = nil
}
}
if s.alternativeFeeProvider != nil {
glog.Info("Using alternative fee provider ", s.ChainConfig.AlternativeEstimateFee)
}
return s, nil
}
@@ -170,14 +187,6 @@ func (b *EthereumRPC) Initialize() error {
return err
}
if b.ChainConfig.AlternativeEstimateFee == "1inch" {
if b.alternativeFeeProvider, err = NewOneInchFeesProvider(b, b.ChainConfig.AlternativeEstimateFeeParams); err != nil {
glog.Error("New1InchFeesProvider error ", err, " Reverting to default estimateFee functionality")
// disable AlternativeEstimateFee logic
b.alternativeFeeProvider = nil
}
}
glog.Info("rpc: block chain ", b.Network)
return nil
@@ -1043,6 +1052,9 @@ func (b *EthereumRPC) EthereumTypeGetEip1559Fees() (*bchain.Eip1559Fees, error)
if err != nil {
return nil, err
}
if len(h.BaseFeePerGas) < blocks {
return nil, nil
}
hs, _ := json.Marshal(h)
baseFee, _ := hexutil.DecodeUint64(h.BaseFeePerGas[blocks-1])

View File

@@ -0,0 +1,190 @@
package eth
import (
"bytes"
"encoding/json"
"math/big"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/trezor/blockbook/bchain"
"github.com/trezor/blockbook/common"
)
// https://gas.api.infura.io/v3/${api_key}/networks/1/suggestedGasFees returns
// {
// "low": {
// "suggestedMaxPriorityFeePerGas": "0.01128",
// "suggestedMaxFeePerGas": "9.919888552",
// "minWaitTimeEstimate": 15000,
// "maxWaitTimeEstimate": 60000
// },
// "medium": {
// "suggestedMaxPriorityFeePerGas": "1.148315423",
// "suggestedMaxFeePerGas": "15.317625653",
// "minWaitTimeEstimate": 15000,
// "maxWaitTimeEstimate": 45000
// },
// "high": {
// "suggestedMaxPriorityFeePerGas": "2",
// "suggestedMaxFeePerGas": "24.78979967",
// "minWaitTimeEstimate": 15000,
// "maxWaitTimeEstimate": 30000
// },
// "estimatedBaseFee": "9.908608552",
// "networkCongestion": 0.004,
// "latestPriorityFeeRange": [
// "0.05",
// "4"
// ],
// "historicalPriorityFeeRange": [
// "0.006381976",
// "155.777346207"
// ],
// "historicalBaseFeeRange": [
// "9.243163495",
// "16.734915363"
// ],
// "priorityFeeTrend": "up",
// "baseFeeTrend": "up",
// "version": "0.0.1"
// }
type infuraFeeResult struct {
MaxPriorityFeePerGas string `json:"suggestedMaxPriorityFeePerGas"`
MaxFeePerGas string `json:"suggestedMaxFeePerGas"`
MinWaitTimeEstimate int `json:"minWaitTimeEstimate"`
MaxWaitTimeEstimate int `json:"maxWaitTimeEstimate"`
}
type infuraFeesResult struct {
BaseFee string `json:"estimatedBaseFee"`
Low infuraFeeResult `json:"low"`
Medium infuraFeeResult `json:"medium"`
High infuraFeeResult `json:"high"`
NetworkCongestion float64 `json:"networkCongestion"`
LatestPriorityFeeRange []string `json:"latestPriorityFeeRange"`
HistoricalPriorityFeeRange []string `json:"historicalPriorityFeeRange"`
HistoricalBaseFeeRange []string `json:"historicalBaseFeeRange"`
PriorityFeeTrend string `json:"priorityFeeTrend"`
BaseFeeTrend string `json:"baseFeeTrend"`
}
type infuraFeeParams struct {
URL string `json:"url"`
PeriodSeconds int `json:"periodSeconds"`
}
type infuraFeeProvider struct {
*alternativeFeeProvider
params infuraFeeParams
apiKey string
}
// NewInfuraFeesProvider initializes https://gas.api.infura.io provider
func NewInfuraFeesProvider(chain bchain.BlockChain, params string) (alternativeFeeProviderInterface, error) {
p := &infuraFeeProvider{alternativeFeeProvider: &alternativeFeeProvider{}}
err := json.Unmarshal([]byte(params), &p.params)
if err != nil {
return nil, err
}
if p.params.URL == "" || p.params.PeriodSeconds == 0 {
return nil, errors.New("NewInfuraFeesProvider: missing config parameters 'url' or 'periodSeconds'.")
}
p.apiKey = os.Getenv("INFURA_API_KEY")
if p.apiKey == "" {
return nil, errors.New("NewInfuraFeesProvider: missing INFURA_API_KEY env variable.")
}
p.params.URL = strings.Replace(p.params.URL, "${api_key}", p.apiKey, -1)
p.chain = chain
go p.FeeDownloader()
return p, nil
}
func (p *infuraFeeProvider) FeeDownloader() {
period := time.Duration(p.params.PeriodSeconds) * time.Second
timer := time.NewTimer(period)
for {
var data infuraFeesResult
err := p.getData(&data)
if err != nil {
glog.Error("infuraFeeProvider.FeeDownloader", err)
} else {
p.processData(&data)
}
<-timer.C
timer.Reset(period)
}
}
func bigIntFromFloatString(s string) *big.Int {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil
}
return big.NewInt(int64(f * 1e9))
}
func infuraFeesFromResult(result *infuraFeeResult) *bchain.Eip1559Fee {
fee := bchain.Eip1559Fee{}
fee.MaxFeePerGas = bigIntFromFloatString(result.MaxFeePerGas)
fee.MaxPriorityFeePerGas = bigIntFromFloatString(result.MaxPriorityFeePerGas)
fee.MinWaitTimeEstimate = result.MinWaitTimeEstimate
fee.MaxWaitTimeEstimate = result.MaxWaitTimeEstimate
return &fee
}
func rangeFromString(feeRange []string) []*big.Int {
if feeRange == nil {
return nil
}
result := make([]*big.Int, len(feeRange))
for i := range feeRange {
result[i] = bigIntFromFloatString(feeRange[i])
}
return result
}
func (p *infuraFeeProvider) processData(data *infuraFeesResult) bool {
fees := bchain.Eip1559Fees{}
fees.BaseFeePerGas = bigIntFromFloatString(data.BaseFee)
fees.High = infuraFeesFromResult(&data.High)
fees.Medium = infuraFeesFromResult(&data.Medium)
fees.Low = infuraFeesFromResult(&data.Low)
fees.NetworkCongestion = data.NetworkCongestion
fees.LatestPriorityFeeRange = rangeFromString(data.LatestPriorityFeeRange)
fees.HistoricalPriorityFeeRange = rangeFromString(data.HistoricalPriorityFeeRange)
fees.HistoricalBaseFeeRange = rangeFromString(data.HistoricalBaseFeeRange)
fees.PriorityFeeTrend = data.PriorityFeeTrend
fees.BaseFeeTrend = data.BaseFeeTrend
p.mux.Lock()
defer p.mux.Unlock()
p.lastSync = time.Now()
p.eip1559Fees = &fees
return true
}
func (p *infuraFeeProvider) getData(res interface{}) error {
var httpData []byte
httpReq, err := http.NewRequest("GET", p.params.URL, bytes.NewBuffer(httpData))
if err != nil {
return err
}
httpReq.Header.Set("Content-Type", "application/json")
httpRes, err := http.DefaultClient.Do(httpReq)
if httpRes != nil {
defer httpRes.Body.Close()
}
if err != nil {
return err
}
if httpRes.StatusCode != http.StatusOK {
return errors.New(p.params.URL + " returned status " + strconv.Itoa(httpRes.StatusCode))
}
return common.SafeDecodeResponseFromReader(httpRes.Body, &res)
}

View File

@@ -101,7 +101,7 @@ func bigIntFromString(s string) *big.Int {
return b
}
func feesFromResult(result *oneInchFeeFeeResult) *bchain.Eip1559Fee {
func oneInchFeesFromResult(result *oneInchFeeFeeResult) *bchain.Eip1559Fee {
fee := bchain.Eip1559Fee{}
fee.MaxFeePerGas = bigIntFromString(result.MaxFeePerGas)
fee.MaxPriorityFeePerGas = bigIntFromString(result.MaxPriorityFeePerGas)
@@ -111,15 +111,14 @@ func feesFromResult(result *oneInchFeeFeeResult) *bchain.Eip1559Fee {
func (p *oneInchFeeProvider) processData(data *oneInchFeeFeesResult) bool {
fees := bchain.Eip1559Fees{}
fees.BaseFeePerGas = bigIntFromString(data.BaseFee)
fees.Instant = feesFromResult(&data.Instant)
fees.High = feesFromResult(&data.High)
fees.Medium = feesFromResult(&data.Medium)
fees.Low = feesFromResult(&data.Low)
fees.Instant = oneInchFeesFromResult(&data.Instant)
fees.High = oneInchFeesFromResult(&data.High)
fees.Medium = oneInchFeesFromResult(&data.Medium)
fees.Low = oneInchFeesFromResult(&data.Low)
p.mux.Lock()
defer p.mux.Unlock()
p.lastSync = time.Now()
p.eip1559Fees = &fees
glog.Infof("oneInchFeesProvider: %+v", p.eip1559Fees)
return true
}
@@ -143,9 +142,3 @@ func (p *oneInchFeeProvider) getData(res interface{}) error {
}
return common.SafeDecodeResponseFromReader(httpRes.Body, &res)
}
func (p *oneInchFeeProvider) GetEip1559Fees() (*bchain.Eip1559Fees, error) {
p.mux.Lock()
defer p.mux.Unlock()
return p.eip1559Fees, nil
}

View File

@@ -168,13 +168,21 @@ type StakingPoolData struct {
type Eip1559Fee struct {
MaxFeePerGas *big.Int `json:"maxFeePerGas"`
MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"`
MinWaitTimeEstimate int `json:"minWaitTimeEstimate,omitempty"`
MaxWaitTimeEstimate int `json:"maxWaitTimeEstimate,omitempty"`
}
// Eip1559Fees
type Eip1559Fees struct {
BaseFeePerGas *big.Int `json:"baseFeePerGas,omitempty"`
Low *Eip1559Fee `json:"low,omitempty"`
Medium *Eip1559Fee `json:"medium,omitempty"`
High *Eip1559Fee `json:"high,omitempty"`
Instant *Eip1559Fee `json:"instant,omitempty"`
BaseFeePerGas *big.Int `json:"baseFeePerGas,omitempty"`
Low *Eip1559Fee `json:"low,omitempty"`
Medium *Eip1559Fee `json:"medium,omitempty"`
High *Eip1559Fee `json:"high,omitempty"`
Instant *Eip1559Fee `json:"instant,omitempty"`
NetworkCongestion float64 `json:"networkCongestion,omitempty"`
LatestPriorityFeeRange []*big.Int `json:"latestPriorityFeeRange,omitempty"`
HistoricalPriorityFeeRange []*big.Int `json:"historicalPriorityFeeRange,omitempty"`
HistoricalBaseFeeRange []*big.Int `json:"historicalBaseFeeRange,omitempty"`
PriorityFeeTrend string `json:"priorityFeeTrend,omitempty"`
BaseFeeTrend string `json:"baseFeeTrend,omitempty"`
}

View File

@@ -15,7 +15,7 @@
},
"ipc": {
"rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}",
"rpc_timeout": 25
"rpc_timeout": 240
},
"backend": {
"package_name": "backend-bsc-archive",
@@ -58,9 +58,13 @@
"block_addresses_to_keep": 600,
"additional_params": {
"address_aliases": true,
"eip1559Fees": true,
"alternative_estimate_fee": "infura",
"alternative_estimate_fee_params": "{\"url\": \"https://gas.api.infura.io/v3/${api_key}/networks/56/suggestedGasFees\", \"periodSeconds\": 20}",
"mempoolTxTimeoutHours": 48,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,
"disableMempoolSync": true,
"fiat_rates": "coingecko",
"fiat_rates_vs_currencies": "AED,ARS,AUD,BDT,BHD,BMD,BRL,CAD,CHF,CLP,CNY,CZK,DKK,EUR,GBP,HKD,HUF,IDR,ILS,INR,JPY,KRW,KWD,LKR,MMK,MXN,MYR,NGN,NOK,NZD,PHP,PKR,PLN,RUB,SAR,SEK,SGD,THB,TRY,TWD,UAH,USD,VEF,VND,ZAR,BTC,ETH",
"fiat_rates_params": "{\"coin\": \"binancecoin\",\"platformIdentifier\": \"binance-smart-chain\",\"platformVsCurrency\": \"bnb\",\"periodSeconds\": 900}",

View File

@@ -60,8 +60,8 @@
"consensusNodeVersion": "http://localhost:7516/eth/v1/node/version",
"address_aliases": true,
"eip1559Fees": true,
"alternative_estimate_fee": "1inch",
"alternative_estimate_fee_params": "{\"url\": \"https://api.1inch.dev/gas-price/v1.5/1\", \"periodSeconds\": 20}",
"alternative_estimate_fee": "infura",
"alternative_estimate_fee_params": "{\"url\": \"https://gas.api.infura.io/v3/${api_key}/networks/1/suggestedGasFees\", \"periodSeconds\": 20}",
"mempoolTxTimeoutHours": 48,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,

View File

@@ -61,6 +61,8 @@
"consensusNodeVersion": "http://localhost:17536/eth/v1/node/version",
"address_aliases": true,
"eip1559Fees": true,
"alternative_estimate_fee": "infura",
"alternative_estimate_fee_params": "{\"url\": \"https://gas.api.infura.io/v3/${api_key}/networks/17000/suggestedGasFees\", \"periodSeconds\": 60}",
"mempoolTxTimeoutHours": 12,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,

View File

@@ -637,13 +637,24 @@ func eip1559FeesToApi(fee *bchain.Eip1559Fee) *api.Eip1559Fee {
return nil
}
apiFee := api.Eip1559Fee{}
if fee != nil {
apiFee.MaxFeePerGas = (*api.Amount)(fee.MaxFeePerGas)
apiFee.MaxPriorityFeePerGas = (*api.Amount)(fee.MaxPriorityFeePerGas)
}
apiFee.MaxFeePerGas = (*api.Amount)(fee.MaxFeePerGas)
apiFee.MaxPriorityFeePerGas = (*api.Amount)(fee.MaxPriorityFeePerGas)
apiFee.MaxWaitTimeEstimate = fee.MaxWaitTimeEstimate
apiFee.MinWaitTimeEstimate = fee.MinWaitTimeEstimate
return &apiFee
}
func eip1559FeeRangeToApi(feeRange []*big.Int) []*api.Amount {
if feeRange == nil {
return nil
}
apiFeeRange := make([]*api.Amount, len(feeRange))
for i := range feeRange {
apiFeeRange[i] = (*api.Amount)(feeRange[i])
}
return apiFeeRange
}
func (s *WebsocketServer) estimateFee(params []byte) (interface{}, error) {
var r WsEstimateFeeReq
err := json.Unmarshal(params, &r)
@@ -679,6 +690,12 @@ func (s *WebsocketServer) estimateFee(params []byte) (interface{}, error) {
eip1559Api.High = eip1559FeesToApi(eip1559.High)
eip1559Api.Medium = eip1559FeesToApi(eip1559.Medium)
eip1559Api.Low = eip1559FeesToApi(eip1559.Low)
eip1559Api.NetworkCongestion = eip1559.NetworkCongestion
eip1559Api.BaseFeeTrend = eip1559.BaseFeeTrend
eip1559Api.PriorityFeeTrend = eip1559.PriorityFeeTrend
eip1559Api.LatestPriorityFeeRange = eip1559FeeRangeToApi(eip1559.LatestPriorityFeeRange)
eip1559Api.HistoricalBaseFeeRange = eip1559FeeRangeToApi(eip1559.HistoricalBaseFeeRange)
eip1559Api.HistoricalPriorityFeeRange = eip1559FeeRangeToApi(eip1559.HistoricalPriorityFeeRange)
}
for i := range r.Blocks {
res[i].FeePerUnit = fee.String()