github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/fourbyte/fourbyte.go (about) 1 package fourbyte 2 3 import ( 4 "encoding/json" 5 "errors" 6 "io/ioutil" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/golang/glog" 13 "github.com/linxGnu/grocksdb" 14 "github.com/trezor/blockbook/bchain" 15 "github.com/trezor/blockbook/db" 16 ) 17 18 // Coingecko is a structure that implements RatesDownloaderInterface 19 type FourByteSignaturesDownloader struct { 20 url string 21 httpTimeoutSeconds time.Duration 22 db *db.RocksDB 23 } 24 25 // NewFourByteSignaturesDownloader initializes the downloader for FourByteSignatures API. 26 func NewFourByteSignaturesDownloader(db *db.RocksDB, url string) (*FourByteSignaturesDownloader, error) { 27 return &FourByteSignaturesDownloader{ 28 url: url, 29 httpTimeoutSeconds: 15 * time.Second, 30 db: db, 31 }, nil 32 } 33 34 // Run starts the FourByteSignatures downloader 35 func (fd *FourByteSignaturesDownloader) Run() { 36 period := time.Hour * 24 37 timer := time.NewTimer(period) 38 for { 39 fd.downloadSignatures() 40 <-timer.C 41 timer.Reset(period) 42 } 43 } 44 45 type signatureData struct { 46 Id int `json:"id"` 47 TextSignature string `json:"text_signature"` 48 HexSignature string `json:"hex_signature"` 49 } 50 51 type signaturesPage struct { 52 Count int `json:"count"` 53 Next string `json:"next"` 54 Results []signatureData `json:"results"` 55 } 56 57 func (fd *FourByteSignaturesDownloader) getPage(url string) (*signaturesPage, error) { 58 req, err := http.NewRequest("GET", url, nil) 59 if err != nil { 60 glog.Errorf("Error creating a new request for %v: %v", url, err) 61 return nil, err 62 } 63 req.Close = true 64 req.Header.Set("Content-Type", "application/json") 65 client := &http.Client{ 66 Timeout: fd.httpTimeoutSeconds, 67 } 68 resp, err := client.Do(req) 69 if err != nil { 70 return nil, err 71 } 72 defer resp.Body.Close() 73 if resp.StatusCode != http.StatusOK { 74 return nil, errors.New("Invalid response status: " + string(resp.Status)) 75 } 76 bodyBytes, err := ioutil.ReadAll(resp.Body) 77 if err != nil { 78 return nil, err 79 } 80 var data signaturesPage 81 err = json.Unmarshal(bodyBytes, &data) 82 if err != nil { 83 glog.Errorf("Error parsing 4byte signatures response from %s: %v", url, err) 84 return nil, err 85 } 86 return &data, nil 87 } 88 89 func (fd *FourByteSignaturesDownloader) getPageWithRetry(url string) (*signaturesPage, error) { 90 for retry := 1; retry <= 16; retry++ { 91 page, err := fd.getPage(url) 92 if err == nil && page != nil { 93 return page, err 94 } 95 glog.Errorf("Error getting 4byte signatures from %s: %v, retry count %d", url, err, retry) 96 timer := time.NewTimer(time.Second * time.Duration(retry)) 97 <-timer.C 98 } 99 return nil, errors.New("Too many retries to 4byte signatures") 100 } 101 102 func parseSignatureFromText(t string) *bchain.FourByteSignature { 103 s := strings.Index(t, "(") 104 e := strings.LastIndex(t, ")") 105 if s < 0 || e < 0 { 106 return nil 107 } 108 var signature bchain.FourByteSignature 109 signature.Name = t[:s] 110 params := t[s+1 : e] 111 if len(params) > 0 { 112 s = 0 113 tupleDepth := 0 114 // parse params as comma separated list 115 // tuple is regarded as one parameter and not parsed further 116 for i, c := range params { 117 if c == ',' && tupleDepth == 0 { 118 signature.Parameters = append(signature.Parameters, params[s:i]) 119 s = i + 1 120 } else if c == '(' { 121 tupleDepth++ 122 } else if c == ')' { 123 tupleDepth-- 124 } 125 } 126 signature.Parameters = append(signature.Parameters, params[s:]) 127 } 128 return &signature 129 } 130 131 func (fd *FourByteSignaturesDownloader) downloadSignatures() { 132 period := time.Millisecond * 100 133 timer := time.NewTimer(period) 134 url := fd.url 135 results := make([]signatureData, 0) 136 glog.Info("FourByteSignaturesDownloader starting download") 137 for { 138 page, err := fd.getPageWithRetry(url) 139 if err != nil { 140 glog.Errorf("Error getting 4byte signatures from %s: %v", url, err) 141 return 142 } 143 if page == nil { 144 glog.Errorf("Empty page from 4byte signatures from %s: %v", url, err) 145 return 146 } 147 glog.Infof("FourByteSignaturesDownloader downloaded %s with %d results", url, len(page.Results)) 148 if len(page.Results) > 0 { 149 fourBytes, err := strconv.ParseUint(page.Results[0].HexSignature, 0, 0) 150 if err != nil { 151 glog.Errorf("Invalid 4byte signature %+v on page %s: %v", page.Results[0], url, err) 152 return 153 } 154 sig, err := fd.db.GetFourByteSignature(uint32(fourBytes), uint32(page.Results[0].Id)) 155 if err != nil { 156 glog.Errorf("db.GetFourByteSignature error %+v on page %s: %v", page.Results[0], url, err) 157 return 158 } 159 // signature is already stored in db, break 160 if sig != nil { 161 break 162 } 163 results = append(results, page.Results...) 164 } 165 if page.Next == "" { 166 // at the end 167 break 168 } 169 url = page.Next 170 // wait a bit to not to flood the server 171 <-timer.C 172 timer.Reset(period) 173 } 174 if len(results) > 0 { 175 glog.Infof("FourByteSignaturesDownloader storing %d new signatures", len(results)) 176 wb := grocksdb.NewWriteBatch() 177 defer wb.Destroy() 178 179 for i := range results { 180 r := &results[i] 181 fourBytes, err := strconv.ParseUint(r.HexSignature, 0, 0) 182 if err != nil { 183 glog.Errorf("Invalid 4byte signature %+v: %v", r, err) 184 return 185 } 186 fbs := parseSignatureFromText(r.TextSignature) 187 if fbs != nil { 188 fd.db.StoreFourByteSignature(wb, uint32(fourBytes), uint32(r.Id), fbs) 189 } else { 190 glog.Errorf("FourByteSignaturesDownloader invalid signature %s", r.TextSignature) 191 } 192 } 193 194 if err := fd.db.WriteBatch(wb); err != nil { 195 glog.Errorf("FourByteSignaturesDownloader failed to store signatures, %v", err) 196 } 197 198 } 199 glog.Infof("FourByteSignaturesDownloader finished") 200 }