Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion oracle/price-feeder/oracle/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,11 @@ func (o *Oracle) SetPrices(ctx context.Context) error {
}
}

o.mtx.Lock()
o.prices = computedPrices
o.lastPriceSyncTS = time.Now()
o.mtx.Unlock()

return nil
}

Expand Down Expand Up @@ -650,7 +654,6 @@ func (o *Oracle) tick(
if err = o.SetPrices(ctx); err != nil {
return err
}
o.lastPriceSyncTS = time.Now()

// Get oracle vote period, next block height, current vote period, and index
// in the vote period.
Expand Down
76 changes: 76 additions & 0 deletions oracle/price-feeder/oracle/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,82 @@ func TestSafeMapContains(t *testing.T) {
}
}

// TestSetPricesAndGetPricesConcurrency tests that concurrent calls to SetPrices
// and GetPrices do not cause a data race. Run with `go test -race` to verify.
func TestSetPricesAndGetPricesConcurrency(t *testing.T) {
oracle := &Oracle{
logger: zerolog.Nop(),
providerPairs: map[string][]types.CurrencyPair{
config.ProviderBinance: {{Base: "ATOM", Quote: "USD"}},
},
chainDenomMapping: map[string]string{
"ATOM": "uatom",
},
priceProviders: map[string]provider.Provider{
config.ProviderBinance: mockProvider{
prices: map[string]provider.TickerPrice{
"ATOMUSD": {
Price: sdk.MustNewDecFromStr("10.00"),
Volume: sdk.MustNewDecFromStr("1000000.00"),
},
},
},
},
failedProviders: make(map[string]error),
paramCache: ParamCache{
params: &oracletypes.Params{
Whitelist: denomList("uatom"),
},
},
providerTimeout: 100 * time.Millisecond,
deviations: make(map[string]sdk.Dec),
}

ctx := context.Background()
var wg sync.WaitGroup
const numGoroutines = 10
const numIterations = 50

// Start goroutines that call SetPrices
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < numIterations; j++ {
_ = oracle.SetPrices(ctx)
}
}()
}

// Start goroutines that call GetPrices
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < numIterations; j++ {
_ = oracle.GetPrices()
}
}()
}

// Start goroutines that call GetLastPriceSyncTimestamp
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < numIterations; j++ {
_ = oracle.GetLastPriceSyncTimestamp()
}
}()
}

wg.Wait()

// Verify the oracle is still in a valid state
prices := oracle.GetPrices()
require.NotNil(t, prices)
}

func TestReportPriceErrMetrics(t *testing.T) {
tests := []struct {
name string
Expand Down