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

     1  package btc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"math"
     7  	"net/http"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/golang/glog"
    12  	"github.com/juju/errors"
    13  	"github.com/trezor/blockbook/bchain"
    14  )
    15  
    16  // https://whatthefee.io returns
    17  // {"index": [3, 6, 9, 12, 18, 24, 36, 48, 72, 96, 144],
    18  // "columns": ["0.0500", "0.2000", "0.5000", "0.8000", "0.9500"],
    19  // "data": [[60, 180, 280, 400, 440], [20, 120, 180, 380, 440],
    20  // [0, 120, 160, 360, 420], [0, 80, 160, 300, 380], [0, 20, 120, 220, 360],
    21  // [0, 20, 100, 180, 300], [0, 0, 80, 140, 240], [0, 0, 60, 100, 180],
    22  // [0, 0, 40, 60, 140], [0, 0, 20, 20, 60], [0, 0, 0, 0, 20]]}
    23  
    24  type whatTheFeeServiceResult struct {
    25  	Index   []int    `json:"index"`
    26  	Columns []string `json:"columns"`
    27  	Data    [][]int  `json:"data"`
    28  }
    29  
    30  type whatTheFeeParams struct {
    31  	URL           string `json:"url"`
    32  	PeriodSeconds int    `periodSeconds:"url"`
    33  }
    34  
    35  type whatTheFeeProvider struct {
    36  	*alternativeFeeProvider
    37  	params        whatTheFeeParams
    38  	probabilities []string
    39  }
    40  
    41  // NewWhatTheFee initializes https://whatthefee.io provider
    42  func NewWhatTheFee(chain bchain.BlockChain, params string) (alternativeFeeProviderInterface, error) {
    43  	var p whatTheFeeProvider
    44  	err := json.Unmarshal([]byte(params), &p.params)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	if p.params.URL == "" || p.params.PeriodSeconds == 0 {
    49  		return nil, errors.New("NewWhatTheFee: Missing parameters")
    50  	}
    51  	p.chain = chain
    52  	go p.whatTheFeeDownloader()
    53  	return &p, nil
    54  }
    55  
    56  func (p *whatTheFeeProvider) whatTheFeeDownloader() {
    57  	period := time.Duration(p.params.PeriodSeconds) * time.Second
    58  	timer := time.NewTimer(period)
    59  	counter := 0
    60  	for {
    61  		var data whatTheFeeServiceResult
    62  		err := p.whatTheFeeGetData(&data)
    63  		if err != nil {
    64  			glog.Error("whatTheFeeGetData ", err)
    65  		} else {
    66  			if p.whatTheFeeProcessData(&data) {
    67  				if counter%60 == 0 {
    68  					p.compareToDefault()
    69  				}
    70  				counter++
    71  			}
    72  		}
    73  		<-timer.C
    74  		timer.Reset(period)
    75  	}
    76  }
    77  
    78  func (p *whatTheFeeProvider) whatTheFeeProcessData(data *whatTheFeeServiceResult) bool {
    79  	if len(data.Index) == 0 || len(data.Index) != len(data.Data) || len(data.Columns) == 0 {
    80  		glog.Errorf("invalid data %+v", data)
    81  		return false
    82  	}
    83  	p.mux.Lock()
    84  	defer p.mux.Unlock()
    85  	p.probabilities = data.Columns
    86  	p.fees = make([]alternativeFeeProviderFee, len(data.Index))
    87  	for i, blocks := range data.Index {
    88  		if len(data.Columns) != len(data.Data[i]) {
    89  			glog.Errorf("invalid data %+v", data)
    90  			return false
    91  		}
    92  		fees := make([]int, len(data.Columns))
    93  		for j, l := range data.Data[i] {
    94  			fees[j] = int(1000 * math.Exp(float64(l)/100))
    95  		}
    96  		p.fees[i] = alternativeFeeProviderFee{
    97  			blocks:   blocks,
    98  			feePerKB: fees[len(fees)/2],
    99  		}
   100  	}
   101  	p.lastSync = time.Now()
   102  	glog.Infof("whatTheFees: %+v", p.fees)
   103  	return true
   104  }
   105  
   106  func (p *whatTheFeeProvider) whatTheFeeGetData(res interface{}) error {
   107  	var httpData []byte
   108  	httpReq, err := http.NewRequest("GET", p.params.URL, bytes.NewBuffer(httpData))
   109  	if err != nil {
   110  		return err
   111  	}
   112  	httpRes, err := http.DefaultClient.Do(httpReq)
   113  	if httpRes != nil {
   114  		defer httpRes.Body.Close()
   115  	}
   116  	if err != nil {
   117  		return err
   118  	}
   119  	if httpRes.StatusCode != 200 {
   120  		return errors.New("whatthefee.io returned status " + strconv.Itoa(httpRes.StatusCode))
   121  	}
   122  	return safeDecodeResponse(httpRes.Body, &res)
   123  }