2021-06-23 00:09:39 +01:00
|
|
|
// Copyright (C) 2021 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package reputation
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"storj.io/common/pb"
|
|
|
|
)
|
|
|
|
|
|
|
|
// UpdateAuditHistoryResponse contains information returned by UpdateAuditHistory.
|
|
|
|
type UpdateAuditHistoryResponse struct {
|
|
|
|
NewScore float64
|
|
|
|
TrackingPeriodFull bool
|
2021-07-28 23:29:46 +01:00
|
|
|
History []byte
|
2021-06-23 00:09:39 +01:00
|
|
|
}
|
|
|
|
|
2022-05-04 22:12:01 +01:00
|
|
|
// DuplicateAuditHistory creates a duplicate (deep copy) of an AuditHistory object.
|
|
|
|
func DuplicateAuditHistory(auditHistory *pb.AuditHistory) *pb.AuditHistory {
|
|
|
|
// argument is not a pointer type, so auditHistory is already a copy.
|
|
|
|
// Just need to copy the slice.
|
|
|
|
if auditHistory == nil {
|
|
|
|
return nil
|
2022-04-20 18:00:25 +01:00
|
|
|
}
|
2022-05-04 22:12:01 +01:00
|
|
|
windows := make([]*pb.AuditWindow, len(auditHistory.Windows))
|
|
|
|
for i := range windows {
|
|
|
|
windows[i] = &pb.AuditWindow{}
|
|
|
|
*windows[i] = *auditHistory.Windows[i]
|
2022-04-20 18:00:25 +01:00
|
|
|
}
|
2022-05-04 22:12:01 +01:00
|
|
|
auditHistory.Windows = windows
|
|
|
|
return auditHistory
|
2022-04-20 18:00:25 +01:00
|
|
|
}
|
2022-05-04 22:15:29 +01:00
|
|
|
|
|
|
|
// AddAuditToHistory adds a single online/not-online event to an AuditHistory.
|
|
|
|
// If the AuditHistory contains windows that are now outside the tracking
|
|
|
|
// period, those windows will be trimmed.
|
2022-05-04 22:12:01 +01:00
|
|
|
func AddAuditToHistory(a *pb.AuditHistory, online bool, auditTime time.Time, config AuditHistoryConfig) error {
|
2022-05-04 22:15:29 +01:00
|
|
|
newAuditWindowStartTime := auditTime.Truncate(config.WindowSize)
|
|
|
|
earliestWindow := newAuditWindowStartTime.Add(-config.TrackingPeriod)
|
|
|
|
// windowsModified is used to determine whether we will need to recalculate the score because windows have been added or removed.
|
|
|
|
windowsModified := false
|
|
|
|
|
|
|
|
// delete windows outside of tracking period scope
|
|
|
|
updatedWindows := a.Windows
|
|
|
|
for i, window := range a.Windows {
|
|
|
|
if window.WindowStart.Before(earliestWindow) {
|
|
|
|
updatedWindows = a.Windows[i+1:]
|
|
|
|
windowsModified = true
|
|
|
|
} else {
|
|
|
|
// windows are in order, so if this window is in the tracking period, we are done deleting windows
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
a.Windows = updatedWindows
|
|
|
|
|
|
|
|
// if there are no windows or the latest window has passed, add another window
|
|
|
|
if len(a.Windows) == 0 || a.Windows[len(a.Windows)-1].WindowStart.Before(newAuditWindowStartTime) {
|
|
|
|
windowsModified = true
|
2022-05-04 22:12:01 +01:00
|
|
|
a.Windows = append(a.Windows, &pb.AuditWindow{WindowStart: newAuditWindowStartTime})
|
2022-05-04 22:15:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
latestIndex := len(a.Windows) - 1
|
|
|
|
if a.Windows[latestIndex].WindowStart.After(newAuditWindowStartTime) {
|
|
|
|
return Error.New("cannot add audit to audit history; window already passed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// add new audit to latest window
|
|
|
|
if online {
|
|
|
|
a.Windows[latestIndex].OnlineCount++
|
|
|
|
}
|
|
|
|
a.Windows[latestIndex].TotalCount++
|
|
|
|
|
|
|
|
// if no windows were added or removed, score does not change
|
|
|
|
if !windowsModified {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(a.Windows) <= 1 {
|
|
|
|
a.Score = 1
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
totalWindowScores := 0.0
|
|
|
|
for i, window := range a.Windows {
|
|
|
|
// do not include last window in score
|
|
|
|
if i+1 == len(a.Windows) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
totalWindowScores += float64(window.OnlineCount) / float64(window.TotalCount)
|
|
|
|
}
|
|
|
|
|
|
|
|
// divide by number of windows-1 because last window is not included
|
|
|
|
a.Score = totalWindowScores / float64(len(a.Windows)-1)
|
|
|
|
return nil
|
|
|
|
}
|
2022-05-07 18:43:32 +01:00
|
|
|
|
|
|
|
// MergeAuditHistories merges two audit histories into one, including all
|
|
|
|
// windows that are present in either input and summing counts for
|
|
|
|
// any windows that appear in _both_ inputs. Any windows that are now outside
|
|
|
|
// the tracking period will be trimmed.
|
|
|
|
//
|
|
|
|
// The history parameter will be mutated to include the windows passed as
|
|
|
|
// addHistory.
|
|
|
|
//
|
|
|
|
// Returns true if the number of windows in the new history is the maximum
|
|
|
|
// possible for the tracking config.
|
|
|
|
func MergeAuditHistories(history *pb.AuditHistory, addHistory []*pb.AuditWindow, config AuditHistoryConfig) (trackingPeriodFull bool) {
|
|
|
|
windows := history.Windows
|
|
|
|
|
|
|
|
for addIndex, windowIndex := 0, 0; addIndex < len(addHistory); {
|
|
|
|
switch {
|
|
|
|
case windowIndex == len(windows):
|
|
|
|
windows = append(windows, &pb.AuditWindow{
|
|
|
|
WindowStart: addHistory[addIndex].WindowStart,
|
|
|
|
})
|
|
|
|
fallthrough
|
|
|
|
case windows[windowIndex].WindowStart.Equal(addHistory[addIndex].WindowStart):
|
|
|
|
windows[windowIndex].TotalCount += addHistory[addIndex].TotalCount
|
|
|
|
windows[windowIndex].OnlineCount += addHistory[addIndex].OnlineCount
|
|
|
|
addIndex++
|
|
|
|
case windows[windowIndex].WindowStart.Before(addHistory[addIndex].WindowStart):
|
|
|
|
windowIndex++
|
|
|
|
case windows[windowIndex].WindowStart.After(addHistory[addIndex].WindowStart):
|
|
|
|
windows = append(windows[:windowIndex+1], windows[windowIndex:]...)
|
|
|
|
windows[windowIndex] = &pb.AuditWindow{
|
|
|
|
WindowStart: addHistory[addIndex].WindowStart,
|
|
|
|
TotalCount: addHistory[addIndex].TotalCount,
|
|
|
|
OnlineCount: addHistory[addIndex].OnlineCount,
|
|
|
|
}
|
|
|
|
addIndex++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// trim off windows that are too old
|
|
|
|
if len(windows) > 0 {
|
|
|
|
cutoffTime := windows[len(windows)-1].WindowStart.Add(-config.TrackingPeriod)
|
|
|
|
for len(windows) > 0 && windows[0].WindowStart.Before(cutoffTime) {
|
|
|
|
windows = windows[1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
history.Windows = windows
|
|
|
|
RecalculateScore(history)
|
|
|
|
|
|
|
|
windowsPerTrackingPeriod := int(config.TrackingPeriod.Seconds() / config.WindowSize.Seconds())
|
|
|
|
trackingPeriodFull = len(history.Windows)-1 >= windowsPerTrackingPeriod
|
|
|
|
|
|
|
|
return trackingPeriodFull
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecalculateScore calculates and assigns the Score field in a pb.AuditHistory object.
|
|
|
|
// The score is calculated by averaging the online percentage in each window
|
|
|
|
// (not including the last).
|
|
|
|
func RecalculateScore(history *pb.AuditHistory) {
|
|
|
|
if len(history.Windows) <= 1 {
|
|
|
|
history.Score = 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
totalWindowScores := float64(0)
|
|
|
|
for i, window := range history.Windows {
|
|
|
|
// do not include last window in score
|
|
|
|
if i+1 == len(history.Windows) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
totalWindowScores += float64(window.OnlineCount) / float64(window.TotalCount)
|
|
|
|
}
|
|
|
|
history.Score = totalWindowScores / float64(len(history.Windows)-1)
|
|
|
|
}
|