github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/bchain/coins/btc/whatthefee.go (about)

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