github.com/number571/tendermint@v0.34.11-gost/internal/p2p/trust/metric.go (about) 1 // Copyright 2017 Tendermint. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package trust 5 6 import ( 7 "math" 8 "time" 9 10 tmsync "github.com/number571/tendermint/internal/libs/sync" 11 "github.com/number571/tendermint/libs/service" 12 ) 13 14 //--------------------------------------------------------------------------------------- 15 16 const ( 17 // The weight applied to the derivative when current behavior is >= previous behavior 18 defaultDerivativeGamma1 = 0 19 20 // The weight applied to the derivative when current behavior is less than previous behavior 21 defaultDerivativeGamma2 = 1.0 22 23 // The weight applied to history data values when calculating the history value 24 defaultHistoryDataWeight = 0.8 25 ) 26 27 // MetricHistoryJSON - history data necessary to save the trust metric 28 type MetricHistoryJSON struct { 29 NumIntervals int `json:"intervals"` 30 History []float64 `json:"history"` 31 } 32 33 // Metric - keeps track of peer reliability 34 // See tendermint/docs/architecture/adr-006-trust-metric.md for details 35 type Metric struct { 36 service.BaseService 37 38 // Mutex that protects the metric from concurrent access 39 mtx tmsync.Mutex 40 41 // Determines the percentage given to current behavior 42 proportionalWeight float64 43 44 // Determines the percentage given to prior behavior 45 integralWeight float64 46 47 // Count of how many time intervals this metric has been tracking 48 numIntervals int 49 50 // Size of the time interval window for this trust metric 51 maxIntervals int 52 53 // The time duration for a single time interval 54 intervalLen time.Duration 55 56 // Stores the trust history data for this metric 57 history []float64 58 59 // Weights applied to the history data when calculating the history value 60 historyWeights []float64 61 62 // The sum of the history weights used when calculating the history value 63 historyWeightSum float64 64 65 // The current number of history data elements 66 historySize int 67 68 // The maximum number of history data elements 69 historyMaxSize int 70 71 // The calculated history value for the current time interval 72 historyValue float64 73 74 // The number of recorded good and bad events for the current time interval 75 bad, good float64 76 77 // While true, history data is not modified 78 paused bool 79 80 // Used during testing in order to control the passing of time intervals 81 testTicker MetricTicker 82 } 83 84 // NewMetric returns a trust metric with the default configuration. 85 // Use Start to begin tracking the quality of peer behavior over time 86 func NewMetric() *Metric { 87 return NewMetricWithConfig(DefaultConfig()) 88 } 89 90 // NewMetricWithConfig returns a trust metric with a custom configuration. 91 // Use Start to begin tracking the quality of peer behavior over time 92 func NewMetricWithConfig(tmc MetricConfig) *Metric { 93 tm := new(Metric) 94 config := customConfig(tmc) 95 96 // Setup using the configuration values 97 tm.proportionalWeight = config.ProportionalWeight 98 tm.integralWeight = config.IntegralWeight 99 tm.intervalLen = config.IntervalLength 100 // The maximum number of time intervals is the tracking window / interval length 101 tm.maxIntervals = int(config.TrackingWindow / tm.intervalLen) 102 // The history size will be determined by the maximum number of time intervals 103 tm.historyMaxSize = intervalToHistoryOffset(tm.maxIntervals) + 1 104 // This metric has a perfect history so far 105 tm.historyValue = 1.0 106 107 tm.BaseService = *service.NewBaseService(nil, "Metric", tm) 108 return tm 109 } 110 111 // OnStart implements Service 112 func (tm *Metric) OnStart() error { 113 if err := tm.BaseService.OnStart(); err != nil { 114 return err 115 } 116 go tm.processRequests() 117 return nil 118 } 119 120 // OnStop implements Service 121 // Nothing to do since the goroutine shuts down by itself via BaseService.Quit() 122 func (tm *Metric) OnStop() {} 123 124 // Returns a snapshot of the trust metric history data 125 func (tm *Metric) HistoryJSON() MetricHistoryJSON { 126 tm.mtx.Lock() 127 defer tm.mtx.Unlock() 128 129 return MetricHistoryJSON{ 130 NumIntervals: tm.numIntervals, 131 History: tm.history, 132 } 133 } 134 135 // Instantiates a trust metric by loading the history data for a single peer. 136 // This is called only once and only right after creation, which is why the 137 // lock is not held while accessing the trust metric struct members 138 func (tm *Metric) Init(hist MetricHistoryJSON) { 139 // Restore the number of time intervals we have previously tracked 140 if hist.NumIntervals > tm.maxIntervals { 141 hist.NumIntervals = tm.maxIntervals 142 } 143 tm.numIntervals = hist.NumIntervals 144 // Restore the history and its current size 145 if len(hist.History) > tm.historyMaxSize { 146 // Keep the history no larger than historyMaxSize 147 last := len(hist.History) - tm.historyMaxSize 148 hist.History = hist.History[last:] 149 } 150 tm.history = hist.History 151 tm.historySize = len(tm.history) 152 // Create the history weight values and weight sum 153 for i := 1; i <= tm.numIntervals; i++ { 154 x := math.Pow(defaultHistoryDataWeight, float64(i)) // Optimistic weight 155 tm.historyWeights = append(tm.historyWeights, x) 156 } 157 158 for _, v := range tm.historyWeights { 159 tm.historyWeightSum += v 160 } 161 // Calculate the history value based on the loaded history data 162 tm.historyValue = tm.calcHistoryValue() 163 } 164 165 // Pause tells the metric to pause recording data over time intervals. 166 // All method calls that indicate events will unpause the metric 167 func (tm *Metric) Pause() { 168 tm.mtx.Lock() 169 defer tm.mtx.Unlock() 170 171 // Pause the metric for now 172 tm.paused = true 173 } 174 175 // BadEvents indicates that an undesirable event(s) took place 176 func (tm *Metric) BadEvents(num int) { 177 tm.mtx.Lock() 178 defer tm.mtx.Unlock() 179 180 tm.unpause() 181 tm.bad += float64(num) 182 } 183 184 // GoodEvents indicates that a desirable event(s) took place 185 func (tm *Metric) GoodEvents(num int) { 186 tm.mtx.Lock() 187 defer tm.mtx.Unlock() 188 189 tm.unpause() 190 tm.good += float64(num) 191 } 192 193 // TrustValue gets the dependable trust value; always between 0 and 1 194 func (tm *Metric) TrustValue() float64 { 195 tm.mtx.Lock() 196 defer tm.mtx.Unlock() 197 198 return tm.calcTrustValue() 199 } 200 201 // TrustScore gets a score based on the trust value always between 0 and 100 202 func (tm *Metric) TrustScore() int { 203 score := tm.TrustValue() * 100 204 205 return int(math.Floor(score)) 206 } 207 208 // NextTimeInterval saves current time interval data and prepares for the following interval 209 func (tm *Metric) NextTimeInterval() { 210 tm.mtx.Lock() 211 defer tm.mtx.Unlock() 212 213 if tm.paused { 214 // Do not prepare for the next time interval while paused 215 return 216 } 217 218 // Add the current trust value to the history data 219 newHist := tm.calcTrustValue() 220 tm.history = append(tm.history, newHist) 221 222 // Update history and interval counters 223 if tm.historySize < tm.historyMaxSize { 224 tm.historySize++ 225 } else { 226 // Keep the history no larger than historyMaxSize 227 last := len(tm.history) - tm.historyMaxSize 228 tm.history = tm.history[last:] 229 } 230 231 if tm.numIntervals < tm.maxIntervals { 232 tm.numIntervals++ 233 // Add the optimistic weight for the new time interval 234 wk := math.Pow(defaultHistoryDataWeight, float64(tm.numIntervals)) 235 tm.historyWeights = append(tm.historyWeights, wk) 236 tm.historyWeightSum += wk 237 } 238 239 // Update the history data using Faded Memories 240 tm.updateFadedMemory() 241 // Calculate the history value for the upcoming time interval 242 tm.historyValue = tm.calcHistoryValue() 243 tm.good = 0 244 tm.bad = 0 245 } 246 247 // SetTicker allows a TestTicker to be provided that will manually control 248 // the passing of time from the perspective of the Metric. 249 // The ticker must be set before Start is called on the metric 250 func (tm *Metric) SetTicker(ticker MetricTicker) { 251 tm.mtx.Lock() 252 defer tm.mtx.Unlock() 253 254 tm.testTicker = ticker 255 } 256 257 // Copy returns a new trust metric with members containing the same values 258 func (tm *Metric) Copy() *Metric { 259 if tm == nil { 260 return nil 261 } 262 263 tm.mtx.Lock() 264 defer tm.mtx.Unlock() 265 266 return &Metric{ 267 proportionalWeight: tm.proportionalWeight, 268 integralWeight: tm.integralWeight, 269 numIntervals: tm.numIntervals, 270 maxIntervals: tm.maxIntervals, 271 intervalLen: tm.intervalLen, 272 history: tm.history, 273 historyWeights: tm.historyWeights, 274 historyWeightSum: tm.historyWeightSum, 275 historySize: tm.historySize, 276 historyMaxSize: tm.historyMaxSize, 277 historyValue: tm.historyValue, 278 good: tm.good, 279 bad: tm.bad, 280 paused: tm.paused, 281 } 282 283 } 284 285 /* Private methods */ 286 287 // This method is for a goroutine that handles all requests on the metric 288 func (tm *Metric) processRequests() { 289 t := tm.testTicker 290 if t == nil { 291 // No test ticker was provided, so we create a normal ticker 292 t = NewTicker(tm.intervalLen) 293 } 294 defer t.Stop() 295 // Obtain the raw channel 296 tick := t.GetChannel() 297 loop: 298 for { 299 select { 300 case <-tick: 301 tm.NextTimeInterval() 302 case <-tm.Quit(): 303 // Stop all further tracking for this metric 304 break loop 305 } 306 } 307 } 308 309 // Wakes the trust metric up if it is currently paused 310 // This method needs to be called with the mutex locked 311 func (tm *Metric) unpause() { 312 // Check if this is the first experience with 313 // what we are tracking since being paused 314 if tm.paused { 315 tm.good = 0 316 tm.bad = 0 317 // New events cause us to unpause the metric 318 tm.paused = false 319 } 320 } 321 322 // Calculates the trust value for the request processing 323 func (tm *Metric) calcTrustValue() float64 { 324 weightedP := tm.proportionalWeight * tm.proportionalValue() 325 weightedI := tm.integralWeight * tm.historyValue 326 weightedD := tm.weightedDerivative() 327 328 tv := weightedP + weightedI + weightedD 329 // Do not return a negative value. 330 if tv < 0 { 331 tv = 0 332 } 333 return tv 334 } 335 336 // Calculates the current score for good/bad experiences 337 func (tm *Metric) proportionalValue() float64 { 338 value := 1.0 339 340 total := tm.good + tm.bad 341 if total > 0 { 342 value = tm.good / total 343 } 344 return value 345 } 346 347 // Strengthens the derivative component when the change is negative 348 func (tm *Metric) weightedDerivative() float64 { 349 var weight float64 = defaultDerivativeGamma1 350 351 d := tm.derivativeValue() 352 if d < 0 { 353 weight = defaultDerivativeGamma2 354 } 355 return weight * d 356 } 357 358 // Calculates the derivative component 359 func (tm *Metric) derivativeValue() float64 { 360 return tm.proportionalValue() - tm.historyValue 361 } 362 363 // Calculates the integral (history) component of the trust value 364 func (tm *Metric) calcHistoryValue() float64 { 365 var hv float64 366 367 for i := 0; i < tm.numIntervals; i++ { 368 hv += tm.fadedMemoryValue(i) * tm.historyWeights[i] 369 } 370 371 return hv / tm.historyWeightSum 372 } 373 374 // Retrieves the actual history data value that represents the requested time interval 375 func (tm *Metric) fadedMemoryValue(interval int) float64 { 376 first := tm.historySize - 1 377 378 if interval == 0 { 379 // Base case 380 return tm.history[first] 381 } 382 383 offset := intervalToHistoryOffset(interval) 384 return tm.history[first-offset] 385 } 386 387 // Performs the update for our Faded Memories process, which allows the 388 // trust metric tracking window to be large while maintaining a small 389 // number of history data values 390 func (tm *Metric) updateFadedMemory() { 391 if tm.historySize < 2 { 392 return 393 } 394 395 end := tm.historySize - 1 396 // Keep the most recent history element 397 for count := 1; count < tm.historySize; count++ { 398 i := end - count 399 // The older the data is, the more we spread it out 400 x := math.Pow(2, float64(count)) 401 // Two history data values are merged into a single value 402 tm.history[i] = ((tm.history[i] * (x - 1)) + tm.history[i+1]) / x 403 } 404 } 405 406 // Map the interval value down to an offset from the beginning of history 407 func intervalToHistoryOffset(interval int) int { 408 // The system maintains 2^m interval values in the form of m history 409 // data values. Therefore, we access the ith interval by obtaining 410 // the history data index = the floor of log2(i) 411 return int(math.Floor(math.Log2(float64(interval)))) 412 }