github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/btc/mempoolspace.go (about)

     1  package btc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"net/http"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/golang/glog"
    11  	"github.com/juju/errors"
    12  	"github.com/trezor/blockbook/bchain"
    13  )
    14  
    15  // https://mempool.space/api/v1/fees/recommended returns
    16  // {"fastestFee":41,"halfHourFee":39,"hourFee":36,"economyFee":36,"minimumFee":20}
    17  
    18  type mempoolSpaceFeeResult struct {
    19  	FastestFee  int `json:"fastestFee"`
    20  	HalfHourFee int `json:"halfHourFee"`
    21  	HourFee     int `json:"hourFee"`
    22  	EconomyFee  int `json:"economyFee"`
    23  	MinimumFee  int `json:"minimumFee"`
    24  }
    25  
    26  type mempoolSpaceFeeParams struct {
    27  	URL           string `json:"url"`
    28  	PeriodSeconds int    `periodSeconds:"url"`
    29  }
    30  
    31  type mempoolSpaceFeeProvider struct {
    32  	*alternativeFeeProvider
    33  	params mempoolSpaceFeeParams
    34  }
    35  
    36  // NewMempoolSpaceFee initializes https://mempool.space provider
    37  func NewMempoolSpaceFee(chain bchain.BlockChain, params string) (alternativeFeeProviderInterface, error) {
    38  	p := &mempoolSpaceFeeProvider{alternativeFeeProvider: &alternativeFeeProvider{}}
    39  	err := json.Unmarshal([]byte(params), &p.params)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	if p.params.URL == "" || p.params.PeriodSeconds == 0 {
    44  		return nil, errors.New("NewWhatTheFee: Missing parameters")
    45  	}
    46  	p.chain = chain
    47  	go p.mempoolSpaceFeeDownloader()
    48  	return p, nil
    49  }
    50  
    51  func (p *mempoolSpaceFeeProvider) mempoolSpaceFeeDownloader() {
    52  	period := time.Duration(p.params.PeriodSeconds) * time.Second
    53  	timer := time.NewTimer(period)
    54  	counter := 0
    55  	for {
    56  		var data mempoolSpaceFeeResult
    57  		err := p.mempoolSpaceFeeGetData(&data)
    58  		if err != nil {
    59  			glog.Error("mempoolSpaceFeeGetData ", err)
    60  		} else {
    61  			if p.mempoolSpaceFeeProcessData(&data) {
    62  				if counter%60 == 0 {
    63  					p.compareToDefault()
    64  				}
    65  				counter++
    66  			}
    67  		}
    68  		<-timer.C
    69  		timer.Reset(period)
    70  	}
    71  }
    72  
    73  func (p *mempoolSpaceFeeProvider) mempoolSpaceFeeProcessData(data *mempoolSpaceFeeResult) bool {
    74  	if data.MinimumFee == 0 || data.EconomyFee == 0 || data.HourFee == 0 || data.HalfHourFee == 0 || data.FastestFee == 0 {
    75  		glog.Errorf("mempoolSpaceFeeProcessData: invalid data %+v", data)
    76  		return false
    77  	}
    78  	p.mux.Lock()
    79  	defer p.mux.Unlock()
    80  	p.fees = make([]alternativeFeeProviderFee, 5)
    81  	// map mempoool.space fees to blocks
    82  
    83  	// FastestFee is for 1 block
    84  	p.fees[0] = alternativeFeeProviderFee{
    85  		blocks:   1,
    86  		feePerKB: data.FastestFee * 1000,
    87  	}
    88  
    89  	// HalfHourFee is for 2-6 blocks
    90  	p.fees[1] = alternativeFeeProviderFee{
    91  		blocks:   6,
    92  		feePerKB: data.HalfHourFee * 1000,
    93  	}
    94  
    95  	// HourFee is for 7-36 blocks
    96  	p.fees[2] = alternativeFeeProviderFee{
    97  		blocks:   36,
    98  		feePerKB: data.HourFee * 1000,
    99  	}
   100  
   101  	// EconomyFee is for 37-200 blocks
   102  	p.fees[3] = alternativeFeeProviderFee{
   103  		blocks:   500,
   104  		feePerKB: data.EconomyFee * 1000,
   105  	}
   106  
   107  	// MinimumFee is for over 500 blocks
   108  	p.fees[4] = alternativeFeeProviderFee{
   109  		blocks:   1000,
   110  		feePerKB: data.MinimumFee * 1000,
   111  	}
   112  
   113  	p.lastSync = time.Now()
   114  	// glog.Infof("mempoolSpaceFees: %+v", p.fees)
   115  	return true
   116  }
   117  
   118  func (p *mempoolSpaceFeeProvider) mempoolSpaceFeeGetData(res interface{}) error {
   119  	var httpData []byte
   120  	httpReq, err := http.NewRequest("GET", p.params.URL, bytes.NewBuffer(httpData))
   121  	if err != nil {
   122  		return err
   123  	}
   124  	httpRes, err := http.DefaultClient.Do(httpReq)
   125  	if httpRes != nil {
   126  		defer httpRes.Body.Close()
   127  	}
   128  	if err != nil {
   129  		return err
   130  	}
   131  	if httpRes.StatusCode != http.StatusOK {
   132  		return errors.New(p.params.URL + " returned status " + strconv.Itoa(httpRes.StatusCode))
   133  	}
   134  	return safeDecodeResponse(httpRes.Body, &res)
   135  }