fix(wallet/coingecko): Fix coingecko crash on race writing to tokens map (#3274)

This commit is contained in:
IvanBelyakoff 2023-03-14 21:59:10 +03:00 committed by GitHub
parent 804b5b43b4
commit 2c9714f0f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 13 deletions

View File

@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/status-im/status-go/services/wallet/thirdparty"
@ -79,12 +80,14 @@ type GeckoToken struct {
}
type Client struct {
client *http.Client
tokens map[string]GeckoToken
client *http.Client
tokens map[string]GeckoToken
tokensURL string
fetchTokensMutex sync.Mutex
}
func NewClient() *Client {
return &Client{client: &http.Client{Timeout: time.Minute}, tokens: make(map[string]GeckoToken)}
return &Client{client: &http.Client{Timeout: time.Minute}, tokens: make(map[string]GeckoToken), tokensURL: fmt.Sprintf("%scoins/list", baseURL)}
}
func (c *Client) DoQuery(url string) (*http.Response, error) {
@ -96,13 +99,27 @@ func (c *Client) DoQuery(url string) (*http.Response, error) {
return resp, nil
}
func mapTokensToSymbols(tokens []GeckoToken, tokenMap map[string]GeckoToken) {
for _, token := range tokens {
if id, ok := coinGeckoMapping[strings.ToUpper(token.Symbol)]; ok {
if id != token.ID {
continue
}
}
tokenMap[strings.ToUpper(token.Symbol)] = token
}
}
func (c *Client) getTokens() (map[string]GeckoToken, error) {
c.fetchTokensMutex.Lock()
defer c.fetchTokensMutex.Unlock()
if len(c.tokens) > 0 {
return c.tokens, nil
}
url := fmt.Sprintf("%scoins/list", baseURL)
resp, err := c.DoQuery(url)
resp, err := c.DoQuery(c.tokensURL)
if err != nil {
return nil, err
}
@ -119,14 +136,7 @@ func (c *Client) getTokens() (map[string]GeckoToken, error) {
return nil, err
}
for _, token := range tokens {
if id, ok := coinGeckoMapping[strings.ToUpper(token.Symbol)]; ok {
if id != token.ID {
continue
}
}
c.tokens[strings.ToUpper(token.Symbol)] = token
}
mapTokensToSymbols(tokens, c.tokens)
return c.tokens, nil
}

View File

@ -0,0 +1,82 @@
package coingecko
import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/stretchr/testify/require"
)
func setupTest(t *testing.T, response []byte) (*httptest.Server, func()) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, err := w.Write(response)
if err != nil {
return
}
}))
return srv, func() {
srv.Close()
}
}
func TestGetTokensSuccess(t *testing.T) {
expected := []GeckoToken{
{
ID: "ethereum",
Symbol: "eth",
Name: "Ethereum",
},
{
ID: "status",
Symbol: "snt",
Name: "Status",
},
}
expectedMap := map[string]GeckoToken{
"ETH": {
ID: "ethereum",
Symbol: "eth",
Name: "Ethereum",
},
"SNT": {
ID: "status",
Symbol: "snt",
Name: "Status",
},
}
response, _ := json.Marshal(expected)
srv, stop := setupTest(t, response)
defer stop()
geckoClient := &Client{
client: srv.Client(),
tokens: make(map[string]GeckoToken),
tokensURL: srv.URL,
}
tokenMap, err := geckoClient.getTokens()
require.NoError(t, err)
require.True(t, reflect.DeepEqual(expectedMap, tokenMap))
}
func TestGetTokensFailure(t *testing.T) {
resp := []byte{}
srv, stop := setupTest(t, resp)
defer stop()
geckoClient := &Client{
client: srv.Client(),
tokens: make(map[string]GeckoToken),
tokensURL: srv.URL,
}
_, err := geckoClient.getTokens()
require.Error(t, err)
}