Governor should handle duplicate coin gecko ids

This commit is contained in:
Bruce Riley 2022-08-10 15:53:48 +00:00 committed by Evan Gray
parent 09b33552df
commit 5ca2faa9a3
3 changed files with 61 additions and 40 deletions

View File

@ -105,7 +105,7 @@ type ChainGovernor struct {
logger *zap.Logger logger *zap.Logger
mutex sync.Mutex mutex sync.Mutex
tokens map[tokenKey]*tokenEntry tokens map[tokenKey]*tokenEntry
tokensByCoinGeckoId map[string]*tokenEntry tokensByCoinGeckoId map[string][]*tokenEntry
chains map[vaa.ChainID]*chainEntry chains map[vaa.ChainID]*chainEntry
msgsToPublish []*common.MessagePublication msgsToPublish []*common.MessagePublication
dayLengthInMinutes int dayLengthInMinutes int
@ -122,7 +122,7 @@ func NewChainGovernor(
db: db, db: db,
logger: logger, logger: logger,
tokens: make(map[tokenKey]*tokenEntry), tokens: make(map[tokenKey]*tokenEntry),
tokensByCoinGeckoId: make(map[string]*tokenEntry), tokensByCoinGeckoId: make(map[string][]*tokenEntry),
chains: make(map[vaa.ChainID]*chainEntry), chains: make(map[vaa.ChainID]*chainEntry),
env: env, env: env,
} }
@ -185,6 +185,17 @@ func (gov *ChainGovernor) initConfig() error {
te := &tokenEntry{cfgPrice: cfgPrice, price: initialPrice, decimals: decimals, symbol: ct.symbol, coinGeckoId: ct.coinGeckoId, token: key} te := &tokenEntry{cfgPrice: cfgPrice, price: initialPrice, decimals: decimals, symbol: ct.symbol, coinGeckoId: ct.coinGeckoId, token: key}
te.updatePrice() te.updatePrice()
gov.tokens[key] = te
// Multiple tokens can share a CoinGecko price, so we keep an array of tokens per CoinGecko ID.
cge, cgExists := gov.tokensByCoinGeckoId[te.coinGeckoId]
if !cgExists {
gov.tokensByCoinGeckoId[te.coinGeckoId] = []*tokenEntry{te}
} else {
cge = append(cge, te)
gov.tokensByCoinGeckoId[te.coinGeckoId] = cge
}
gov.logger.Info("cgov: will monitor token:", zap.Stringer("chain", key.chain), gov.logger.Info("cgov: will monitor token:", zap.Stringer("chain", key.chain),
zap.Stringer("addr", key.addr), zap.Stringer("addr", key.addr),
zap.String("symbol", te.symbol), zap.String("symbol", te.symbol),
@ -193,9 +204,6 @@ func (gov *ChainGovernor) initConfig() error {
zap.Int64("decimals", dec), zap.Int64("decimals", dec),
zap.Int64("origDecimals", ct.decimals), zap.Int64("origDecimals", ct.decimals),
) )
gov.tokens[key] = te
gov.tokensByCoinGeckoId[te.coinGeckoId] = te
} }
if len(gov.tokens) == 0 { if len(gov.tokens) == 0 {

View File

@ -109,13 +109,13 @@ func (gov *ChainGovernor) queryCoinGecko() {
gov.mutex.Lock() gov.mutex.Lock()
defer gov.mutex.Unlock() defer gov.mutex.Unlock()
localTokenMap := make(map[string]*tokenEntry) localTokenMap := make(map[string][]*tokenEntry)
for coinGeckoId, te := range gov.tokensByCoinGeckoId { for coinGeckoId, cge := range gov.tokensByCoinGeckoId {
localTokenMap[coinGeckoId] = te localTokenMap[coinGeckoId] = cge
} }
for coinGeckoId, data := range result { for coinGeckoId, data := range result {
te, exists := gov.tokensByCoinGeckoId[coinGeckoId] cge, exists := gov.tokensByCoinGeckoId[coinGeckoId]
if exists { if exists {
price, ok := data.(map[string]interface{})["usd"].(float64) price, ok := data.(map[string]interface{})["usd"].(float64)
if !ok { if !ok {
@ -123,18 +123,21 @@ func (gov *ChainGovernor) queryCoinGecko() {
// By continuing, we leave this one in the local map so the price will get reverted below. // By continuing, we leave this one in the local map so the price will get reverted below.
continue continue
} }
te.coinGeckoPrice = big.NewFloat(price)
te.updatePrice()
te.priceTime = now
gov.logger.Info("cgov: updated price", for _, te := range cge {
zap.String("symbol", te.symbol), te.coinGeckoPrice = big.NewFloat(price)
zap.String("coinGeckoId", te.updatePrice()
te.coinGeckoId), te.priceTime = now
zap.Stringer("price", te.price),
zap.Stringer("cfgPrice", te.cfgPrice), gov.logger.Info("cgov: updated price",
zap.Stringer("coinGeckoPrice", te.coinGeckoPrice), zap.String("symbol", te.symbol),
) zap.String("coinGeckoId",
te.coinGeckoId),
zap.Stringer("price", te.price),
zap.Stringer("cfgPrice", te.cfgPrice),
zap.Stringer("coinGeckoPrice", te.coinGeckoPrice),
)
}
delete(localTokenMap, coinGeckoId) delete(localTokenMap, coinGeckoId)
} else { } else {
@ -143,8 +146,29 @@ func (gov *ChainGovernor) queryCoinGecko() {
} }
if len(localTokenMap) != 0 { if len(localTokenMap) != 0 {
for _, te := range localTokenMap { for _, lcge := range localTokenMap {
gov.logger.Error("cgov: did not receive a CoinGecko response for symbol, reverting to configured price", for _, te := range lcge {
gov.logger.Error("cgov: did not receive a CoinGecko response for symbol, reverting to configured price",
zap.String("symbol", te.symbol),
zap.String("coinGeckoId",
te.coinGeckoId),
zap.Stringer("cfgPrice", te.cfgPrice),
)
te.price = te.cfgPrice
// Don't update the timestamp so we'll know when we last received an update from CoinGecko.
}
}
}
}
func (gov *ChainGovernor) revertAllPrices() {
gov.mutex.Lock()
defer gov.mutex.Unlock()
for _, cge := range gov.tokensByCoinGeckoId {
for _, te := range cge {
gov.logger.Error("cgov: reverting to configured price",
zap.String("symbol", te.symbol), zap.String("symbol", te.symbol),
zap.String("coinGeckoId", zap.String("coinGeckoId",
te.coinGeckoId), te.coinGeckoId),
@ -157,23 +181,6 @@ func (gov *ChainGovernor) queryCoinGecko() {
} }
} }
func (gov *ChainGovernor) revertAllPrices() {
gov.mutex.Lock()
defer gov.mutex.Unlock()
for _, te := range gov.tokensByCoinGeckoId {
gov.logger.Error("cgov: reverting to configured price",
zap.String("symbol", te.symbol),
zap.String("coinGeckoId",
te.coinGeckoId),
zap.Stringer("cfgPrice", te.cfgPrice),
)
te.price = te.cfgPrice
// Don't update the timestamp so we'll know when we last received an update from CoinGecko.
}
}
// We should use the max(coinGeckoPrice, configuredPrice) as our price for computing notional value. // We should use the max(coinGeckoPrice, configuredPrice) as our price for computing notional value.
func (te tokenEntry) updatePrice() { func (te tokenEntry) updatePrice() {
if (te.coinGeckoPrice == nil) || (te.coinGeckoPrice.Cmp(te.cfgPrice) < 0) { if (te.coinGeckoPrice == nil) || (te.coinGeckoPrice.Cmp(te.cfgPrice) < 0) {

View File

@ -78,7 +78,13 @@ func (gov *ChainGovernor) setTokenForTesting(tokenChainID vaa.ChainID, tokenAddr
key := tokenKey{chain: vaa.ChainID(tokenChainID), addr: tokenAddr} key := tokenKey{chain: vaa.ChainID(tokenChainID), addr: tokenAddr}
te := &tokenEntry{cfgPrice: bigPrice, price: bigPrice, decimals: decimals, symbol: symbol, coinGeckoId: symbol, token: key} te := &tokenEntry{cfgPrice: bigPrice, price: bigPrice, decimals: decimals, symbol: symbol, coinGeckoId: symbol, token: key}
gov.tokens[key] = te gov.tokens[key] = te
gov.tokensByCoinGeckoId[symbol] = te cge, cgExists := gov.tokensByCoinGeckoId[te.coinGeckoId]
if !cgExists {
gov.tokensByCoinGeckoId[te.coinGeckoId] = []*tokenEntry{te}
} else {
cge = append(cge, te)
gov.tokensByCoinGeckoId[te.coinGeckoId] = cge
}
return nil return nil
} }