Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix][Feeds] Fix Grogu #486

Merged
merged 1 commit into from
Nov 26, 2024
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
34 changes: 15 additions & 19 deletions grogu/signaller/signaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,16 +240,16 @@ func (s *Signaller) filterAndPrepareSignalPrices(
continue
}

if !s.isPriceValid(price, currentTime) {
continue
}

signalPrice, err := convertPriceData(price)
if err != nil {
s.logger.Debug("[Signaller] failed to parse price data: %v", err)
continue
}

if !s.isPriceValid(signalPrice, currentTime) {
continue
}

if s.isNonUrgentUnavailablePrices(signalPrice, currentTime.Unix()) {
s.logger.Debug("[Signaller] non-urgent unavailable price: %v", signalPrice)
continue
Expand Down Expand Up @@ -278,38 +278,34 @@ func (s *Signaller) isNonUrgentUnavailablePrices(
}

func (s *Signaller) isPriceValid(
price *bothan.Price,
newPrice types.SignalPrice,
now time.Time,
) bool {
// Check if the price is supported and required to be submitted
feed, ok := s.signalIDToFeed[price.SignalId]
feed, ok := s.signalIDToFeed[newPrice.SignalID]
if !ok {
return false
}

// Get the last price submitted by the validator, if it doesn't exist, it is valid to be sent
valPrice, ok := s.signalIDToValidatorPrice[price.SignalId]
oldPrice, ok := s.signalIDToValidatorPrice[newPrice.SignalID]
if !ok {
return true
}

// If the last price exists, check if the price can be updated
if s.shouldUpdatePrice(feed, valPrice, price.Price, now) {
return true
}

return false
return s.shouldUpdatePrice(feed, oldPrice, newPrice, now)
}

func (s *Signaller) shouldUpdatePrice(
feed types.FeedWithDeviation,
valPrice types.ValidatorPrice,
newPrice uint64,
oldPrice types.ValidatorPrice,
newPrice types.SignalPrice,
now time.Time,
) bool {
// thresholdTime is the time when the price can be updated.
// add TimeBuffer to make sure the thresholdTime is not too early.
thresholdTime := time.Unix(valPrice.Timestamp+s.params.CooldownTime+TimeBuffer, 0)
thresholdTime := time.Unix(oldPrice.Timestamp+s.params.CooldownTime+TimeBuffer, 0)

if now.Before(thresholdTime) {
return false
Expand All @@ -319,7 +315,7 @@ func (s *Signaller) shouldUpdatePrice(
assignedTime := calculateAssignedTime(
s.valAddress,
feed.Interval,
valPrice.Timestamp,
oldPrice.Timestamp,
s.distributionOffsetPercentage,
s.distributionStartPercentage,
)
Expand All @@ -328,10 +324,10 @@ func (s *Signaller) shouldUpdatePrice(
return true
}

// Check if the price is deviated from the last submission, if it is, add it to the list of prices to update
if isDeviated(feed.DeviationBasisPoint, valPrice.Price, newPrice) {
if oldPrice.SignalPriceStatus != newPrice.Status {
return true
}

return false
// Check if the price is deviated from the last submission, if it is, add it to the list of prices to update
return isDeviated(feed.DeviationBasisPoint, oldPrice.Price, newPrice.Price)
}
95 changes: 46 additions & 49 deletions grogu/signaller/signaller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ func (s *SignallerTestSuite) SetupTest() {
Interval: 60,
DeviationBasisPoint: 50,
},
{
SignalID: "signal2",
Power: 60000000000,
Interval: 60,
DeviationBasisPoint: 50,
},
},
}}, nil).
AnyTimes()
Expand Down Expand Up @@ -224,25 +230,18 @@ func (s *SignallerTestSuite) TestIsPriceValid() {
// Update internal variables
s.TestUpdateInternalVariables()

priceData := &bothan.Price{
SignalId: "signal1",
Price: 10000,
Status: bothan.Status_STATUS_AVAILABLE,
priceData := feeds.SignalPrice{
Status: feeds.SIGNAL_PRICE_STATUS_AVAILABLE,
Price: 10000,
}

// Test with time before the assigned time
beforeAssignedTime := time.Unix(s.assignedTime.Unix()-1, 0)
isValid := s.Signaller.isPriceValid(priceData, beforeAssignedTime)
s.Require().False(isValid)
// Test with price is not required to be submitted
priceData.SignalID = "signal3"
s.Require().False(s.Signaller.isPriceValid(priceData, s.assignedTime))

// Test with time at the assigned time
isValid = s.Signaller.isPriceValid(priceData, s.assignedTime)
s.Require().True(isValid)

// Test with time at the start of the interval
startOfInterval := time.Unix(0, 0)
isValid = s.Signaller.isPriceValid(priceData, startOfInterval)
s.Require().False(isValid)
// Test with price is required to be submitted and not exist yet
priceData.SignalID = "signal2"
s.Require().True(s.Signaller.isPriceValid(priceData, s.assignedTime))
}

func (s *SignallerTestSuite) TestShouldUpdatePrice() {
Expand All @@ -256,46 +255,44 @@ func (s *SignallerTestSuite) TestShouldUpdatePrice() {
}

valPrice := feeds.ValidatorPrice{
SignalID: "signal1",
Price: 10000,
Timestamp: 0,
SignalID: "signal1",
Price: 10000,
Timestamp: 0,
SignalPriceStatus: feeds.SIGNAL_PRICE_STATUS_AVAILABLE,
}

// Test with new price positive deviation
thresholdTime := time.Unix(valPrice.Timestamp+s.Signaller.params.CooldownTime+TimeBuffer, 0)
newPrice := uint64(10050)

shouldUpdate := s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime)
s.Require().True(shouldUpdate)

// Test with new price negative deviation
newPrice = uint64(9950)

shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime)
s.Require().True(shouldUpdate)

// Test with new price within deviation
newPrice = uint64(10025)
newPrice := feeds.SignalPrice{
Price: 10000,
Status: feeds.SIGNAL_PRICE_STATUS_AVAILABLE,
}

shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime)
s.Require().False(shouldUpdate)
thresholdTime := time.Unix(valPrice.Timestamp+s.Signaller.params.CooldownTime+TimeBuffer, 0)

// Test with new price outside deviation
newPrice = uint64(10075)
// Test case: Time before thresholdTime, should not update
s.Require().False(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime.Add(-time.Second)))

shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, thresholdTime)
s.Require().True(shouldUpdate)
// Test case: Time after thresholdTime and assignedTime
assignedTime := calculateAssignedTime(
s.Signaller.valAddress,
feed.Interval,
valPrice.Timestamp,
s.Signaller.distributionOffsetPercentage,
s.Signaller.distributionStartPercentage,
)
s.Require().True(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(time.Second)))

// Test with time before threshold time, price outside deviation
newPrice = uint64(10075)
beforeThresholdTime := time.Unix(valPrice.Timestamp+s.Signaller.params.CooldownTime, 0)
// Test case: SignalPriceStatus changed, should update
newPrice.Status = feeds.SIGNAL_PRICE_STATUS_AVAILABLE
s.Require().False(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second)))

shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, beforeThresholdTime)
s.Require().False(shouldUpdate)
newPrice.Status = feeds.SIGNAL_PRICE_STATUS_UNAVAILABLE
s.Require().True(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second)))

// Test with time at assigned time, price within deviation
newPrice = uint64(10025)
// Test case: Price deviated
newPrice.Status = feeds.SIGNAL_PRICE_STATUS_AVAILABLE
newPrice.Price = 11000 // More than deviationBasisPoint
s.Require().True(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second)))

shouldUpdate = s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, s.assignedTime)
s.Require().True(shouldUpdate)
newPrice.Price = 10025 // Within deviationBasisPoint
s.Require().False(s.Signaller.shouldUpdatePrice(feed, valPrice, newPrice, assignedTime.Add(-time.Second)))
}
7 changes: 6 additions & 1 deletion grogu/signaller/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
// exceeds a given threshold in basis points.

// Parameters:
// - deviationBasisPoint: the allowable deviation in basis points (1/1000th)
// - deviationBasisPoint: the allowable deviation in basis points (1/10000th)
// - oldPrice: the original price
// - newPrice: the new price to compare against the original
//
Expand All @@ -31,6 +31,11 @@ import (
// original price and multiplying by 10000.
// 3. Check if the calculated deviation meets or exceeds the allowable deviation.
func isDeviated(deviationBasisPoint int64, oldPrice uint64, newPrice uint64) bool {
// If the old price is zero and the new price is non-zero, consider it a deviation
if oldPrice == 0 {
return newPrice != 0
}

// Calculate the deviation
diff := math.Abs(float64(newPrice) - float64(oldPrice))
dev := int64((diff * 10000) / float64(oldPrice))
Expand Down
3 changes: 3 additions & 0 deletions grogu/signaller/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func TestIsDeviated(t *testing.T) {
{"Below threshold", 100, 1000, 1001, false},
{"Exact threshold", 100, 1000, 1010, true},
{"Above threshold", 100, 1000, 1100, true},
{"Zero old price", 100, 0, 1000, true},
{"Zero new price", 100, 1000, 0, true},
{"Zero old and new price", 100, 0, 0, false},
}

for _, tt := range tests {
Expand Down
Loading