github.com/cerberus-wallet/blockbook@v0.3.2/bchain/coins/btc/whatthefee.go (about)

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