Skip to content

Commit

Permalink
Optimize Pricefeed EndBlocker (#1851)
Browse files Browse the repository at this point in the history
* optimize pricefeed endblocker to iterate all markets only once to remove
overhead of opening and closing iterator for each market individually.
In addition, extend tests to cover 100% of abci and price updating
behavior.

* use test cases that can't be confused with mean to ensure median is
always used
  • Loading branch information
nddeluca authored Mar 26, 2024
1 parent 3afb656 commit 6737904
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 12 deletions.
13 changes: 1 addition & 12 deletions x/pricefeed/abci.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package pricefeed

import (
"errors"
"time"

"github.com/cosmos/cosmos-sdk/telemetry"
Expand All @@ -14,15 +13,5 @@ import (
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)

// Update the current price of each asset.
for _, market := range k.GetMarkets(ctx) {
if !market.Active {
continue
}

err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil && !errors.Is(err, types.ErrNoValidPrice) {
panic(err)
}
}
k.SetCurrentPricesForAllMarkets(ctx)
}
20 changes: 20 additions & 0 deletions x/pricefeed/abci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pricefeed_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/pricefeed"
"github.com/kava-labs/kava/x/pricefeed/keeper"
"github.com/kava-labs/kava/x/pricefeed/testutil"
)

func TestEndBlocker_UpdatesMultipleMarkets(t *testing.T) {
testutil.SetCurrentPrices_PriceCalculations(t, func(ctx sdk.Context, keeper keeper.Keeper) {
pricefeed.EndBlocker(ctx, keeper)
})

testutil.SetCurrentPrices_EventEmission(t, func(ctx sdk.Context, keeper keeper.Keeper) {
pricefeed.EndBlocker(ctx, keeper)
})
}
68 changes: 68 additions & 0 deletions x/pricefeed/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,74 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
return nil
}

// SetCurrentPricesForAllMarkets updates the price of an asset to the median of all valid oracle inputs
func (k Keeper) SetCurrentPricesForAllMarkets(ctx sdk.Context) {
orderedMarkets := []string{}
marketPricesByID := make(map[string]types.CurrentPrices)

for _, market := range k.GetMarkets(ctx) {
if market.Active {
orderedMarkets = append(orderedMarkets, market.MarketID)
marketPricesByID[market.MarketID] = types.CurrentPrices{}
}
}

iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.RawPriceFeedPrefix)
for ; iterator.Valid(); iterator.Next() {
var postedPrice types.PostedPrice
k.cdc.MustUnmarshal(iterator.Value(), &postedPrice)

prices, found := marketPricesByID[postedPrice.MarketID]
if !found {
continue
}

// filter out expired prices
if postedPrice.Expiry.After(ctx.BlockTime()) {
marketPricesByID[postedPrice.MarketID] = append(prices, types.NewCurrentPrice(postedPrice.MarketID, postedPrice.Price))
}
}
iterator.Close()

for _, marketID := range orderedMarkets {
// store current price
validPrevPrice := true
prevPrice, err := k.GetCurrentPrice(ctx, marketID)
if err != nil {
validPrevPrice = false
}

notExpiredPrices, _ := marketPricesByID[marketID]

if len(notExpiredPrices) == 0 {
// NOTE: The current price stored will continue storing the most recent (expired)
// price if this is not set.
// This zero's out the current price stored value for that market and ensures
// that CDP methods that GetCurrentPrice will return error.
k.setCurrentPrice(ctx, marketID, types.CurrentPrice{})
continue
}

medianPrice := k.CalculateMedianPrice(notExpiredPrices)

// check case that market price was not set in genesis
//if validPrevPrice && !medianPrice.Equal(prevPrice.Price) {
if validPrevPrice && !medianPrice.Equal(prevPrice.Price) {
// only emit event if price has changed
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeMarketPriceUpdated,
sdk.NewAttribute(types.AttributeMarketID, marketID),
sdk.NewAttribute(types.AttributeMarketPrice, medianPrice.String()),
),
)
}

currentPrice := types.NewCurrentPrice(marketID, medianPrice)
k.setCurrentPrice(ctx, marketID, currentPrice)
}
}

func (k Keeper) setCurrentPrice(ctx sdk.Context, marketID string, currentPrice types.CurrentPrice) {
store := ctx.KVStore(k.key)
store.Set(types.CurrentPriceKey(marketID), k.cdc.MustMarshal(&currentPrice))
Expand Down
33 changes: 33 additions & 0 deletions x/pricefeed/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"errors"
"testing"
"time"

Expand All @@ -11,6 +12,8 @@ import (
tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types"

"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/pricefeed/keeper"
"github.com/kava-labs/kava/x/pricefeed/testutil"
"github.com/kava-labs/kava/x/pricefeed/types"
)

Expand Down Expand Up @@ -236,3 +239,33 @@ func TestKeeper_ExpiredSetCurrentPrices(t *testing.T) {
_, err = keeper.GetCurrentPrice(ctx, "tstusd")
require.ErrorIs(t, types.ErrNoValidPrice, err, "current prices should be invalid")
}

func TestKeeper_SetCurrentPricesForAllMarkets_PriceUpdate(t *testing.T) {
testutil.SetCurrentPrices_PriceCalculations(t, func(ctx sdk.Context, keeper keeper.Keeper) {
keeper.SetCurrentPricesForAllMarkets(ctx)
})
}

func TestKeeper_SetCurrentPricesForAllMarkets_EventEmission(t *testing.T) {
testutil.SetCurrentPrices_EventEmission(t, func(ctx sdk.Context, keeper keeper.Keeper) {
keeper.SetCurrentPricesForAllMarkets(ctx)
})
}

func TestKeeper_SetCurrentPrices_MatchesAllMarketsBehavior(t *testing.T) {
testFunc := func(ctx sdk.Context, k keeper.Keeper) {
for _, market := range k.GetMarkets(ctx) {
if !market.Active {
continue
}

err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil && !errors.Is(err, types.ErrNoValidPrice) {
panic(err)
}
}
}

testutil.SetCurrentPrices_PriceCalculations(t, testFunc)
testutil.SetCurrentPrices_EventEmission(t, testFunc)
}
Loading

0 comments on commit 6737904

Please sign in to comment.