From a724ffab25c266dc9e9c6af8f889c9d49b3a28c7 Mon Sep 17 00:00:00 2001 From: caffix Date: Wed, 15 Nov 2017 17:59:48 -0500 Subject: [PATCH] added changes based on PR comments to the proposal --- docs/architecture/adr-006-trust-metric.md | 32 +++--- p2p/trust/trustmetric.go | 129 +++++++++++----------- p2p/trust/trustmetric_test.go | 14 +-- 3 files changed, 85 insertions(+), 90 deletions(-) diff --git a/docs/architecture/adr-006-trust-metric.md b/docs/architecture/adr-006-trust-metric.md index 961830cf9..ec8a0cce7 100644 --- a/docs/architecture/adr-006-trust-metric.md +++ b/docs/architecture/adr-006-trust-metric.md @@ -38,7 +38,7 @@ where *R*[*i*] denotes the raw trust value at time interval *i* (where *i* == 0 `H[i] = ` ![formula1](img/formula1.png "Weighted Sum Formula") -The weights can be chosen either optimistically or pessimistically. With the history value available, we can now finish calculating the integral value: +The weights can be chosen either optimistically or pessimistically. An optimistic weight creates larger weights for newer history data values, while the the pessimistic weight creates larger weights for time intervals with lower scores. The default weights used during the calculation of the history value are optimistic and calculated as *Wk* = 0.8^*k*, for time interval *k*. With the history value available, we can now finish calculating the integral value: ```math (2) Integral Value = b * H[i] @@ -49,13 +49,13 @@ Where *H*[*i*] denotes the history value at time interval *i* and *b* is the wei ```math D[i] = R[i] – H[i] -(3) Derivative Value = (c * D[i]) * D[i] +(3) Derivative Value = c(D[i]) * D[i] ``` -Where the value of *c* is selected based on the *D*[*i*] value relative to zero. With the three components brought together, our trust value equation is calculated as follows: +Where the value of *c* is selected based on the *D*[*i*] value relative to zero. The default selection process makes *c* equal to 0 unless *D*[*i*] is a negative value, in which case c is equal to 1. The result is that the maximum penalty is applied when current behavior is lower than previously experienced behavior. If the current behavior is better than the previously experienced behavior, then the Derivative Value has no impact on the trust value. With the three components brought together, our trust value equation is calculated as follows: ```math -TrustValue[i] = a * R[i] + b * H[i] + (c * D[i]) * D[i] +TrustValue[i] = a * R[i] + b * H[i] + c(D[i]) * D[i] ``` As a performance optimization that will keep the amount of raw interval data being saved to a reasonable size of *m*, while allowing us to represent 2^*m* - 1 history intervals, we can employ the fading memories technique that will trade space and time complexity for the precision of the history data values by summarizing larger quantities of less recent values. While our equation above attempts to access up to *maxH* (which can be 2^*m* - 1), we will map those requests down to *m* values using equation 4 below: @@ -99,17 +99,11 @@ func (tm *TrustMetric) Pause() {} // Stop tells the metric to stop recording data over time intervals func (tm *TrustMetric) Stop() {} -// BadEvent indicates that an undesirable event took place -func (tm *TrustMetric) BadEvent() {} +// BadEvents indicates that an undesirable event(s) took place +func (tm *TrustMetric) BadEvents(num int) {} -// AddBadEvents acknowledges multiple undesirable events -func (tm *TrustMetric) AddBadEvents(num int) {} - -// GoodEvent indicates that a desirable event took place -func (tm *TrustMetric) GoodEvent() {} - -// AddGoodEvents acknowledges multiple desirable events -func (tm *TrustMetric) AddGoodEvents(num int) {} +// GoodEvents indicates that a desirable event(s) took place +func (tm *TrustMetric) GoodEvents(num int) {} // TrustValue gets the dependable trust value; always between 0 and 1 func (tm *TrustMetric) TrustValue() float64 {} @@ -125,7 +119,7 @@ func NewMetric() *TrustMetric {} tm := NewMetric() -tm.BadEvent() +tm.BadEvents(1) score := tm.TrustScore() tm.Stop() @@ -170,9 +164,9 @@ config := TrustMetricConfig{ tm := NewMetricWithConfig(config) -tm.AddBadEvents(10) +tm.BadEvents(10) tm.Pause() -tm.GoodEvent() // becomes active again +tm.GoodEvents(1) // becomes active again ``` @@ -217,7 +211,7 @@ db := dbm.NewDB("trusthistory", "goleveldb", dirPathStr) tms := NewTrustMetricStore(db, DefaultConfig()) tm := tms.GetPeerTrustMetric(key) -tm.BadEvent() +tm.BadEvents(1) tms.PeerDisconnected(key) @@ -225,7 +219,7 @@ tms.PeerDisconnected(key) ## Status -Proposed. +Approved. ## Consequences diff --git a/p2p/trust/trustmetric.go b/p2p/trust/trustmetric.go index 39c24c247..84a11b1ca 100644 --- a/p2p/trust/trustmetric.go +++ b/p2p/trust/trustmetric.go @@ -30,9 +30,6 @@ type TrustMetricStore struct { // This configuration will be used when creating new TrustMetrics config TrustMetricConfig - - // This channel is used to stop the store go-routine - stop chan int } // NewTrustMetricStore returns a store that saves data to the DB @@ -42,7 +39,6 @@ func NewTrustMetricStore(db dbm.DB, tmc TrustMetricConfig) *TrustMetricStore { peerMetrics: make(map[string]*TrustMetric), db: db, config: tmc, - stop: make(chan int, 2), } tms.BaseService = *cmn.NewBaseService(nil, "TrustMetricStore", tms) @@ -57,14 +53,13 @@ func (tms *TrustMetricStore) OnStart() error { defer tms.mtx.Unlock() tms.loadFromDB() - go tms.periodicSave() + go tms.saveRoutine() return nil } // OnStop implements Service func (tms *TrustMetricStore) OnStop() { - // Stop the store periodic save go-routine - tms.stop <- 1 + tms.BaseService.OnStop() tms.mtx.Lock() defer tms.mtx.Unlock() @@ -76,7 +71,6 @@ func (tms *TrustMetricStore) OnStop() { // Make the final trust history data save tms.saveToDB() - tms.BaseService.OnStop() } // Size returns the number of entries in the trust metric store @@ -130,7 +124,7 @@ type peerHistoryJSON struct { History []float64 `json:"history"` } -// Loads the history data for the Peer identified by key from the store DB. +// Loads the history data for all peers from the store DB // cmn.Panics if file is corrupt func (tms *TrustMetricStore) loadFromDB() bool { // Obtain the history data we have so far @@ -157,10 +151,21 @@ func (tms *TrustMetricStore) loadFromDB() bool { tm.numIntervals = p.NumIntervals // Restore the history and its current size if len(p.History) > tm.historyMaxSize { - p.History = p.History[:tm.historyMaxSize] + // Keep the history no larger than historyMaxSize + last := len(p.History) - tm.historyMaxSize + p.History = p.History[last:] } tm.history = p.History tm.historySize = len(tm.history) + // Create the history weight values and weight sum + for i := 1; i <= tm.numIntervals; i++ { + x := math.Pow(defaultHistoryDataWeight, float64(i)) // Optimistic weight + tm.historyWeights = append(tm.historyWeights, x) + } + + for _, v := range tm.historyWeights { + tm.historyWeightSum += v + } // Calculate the history value based on the loaded history data tm.historyValue = tm.calcHistoryValue() // Load the peer trust metric into the store @@ -193,7 +198,7 @@ func (tms *TrustMetricStore) saveToDB() { } // Periodically saves the trust history data to the DB -func (tms *TrustMetricStore) periodicSave() { +func (tms *TrustMetricStore) saveRoutine() { t := time.NewTicker(defaultStorePeriodicSaveInterval) defer t.Stop() loop: @@ -203,7 +208,7 @@ loop: tms.mtx.Lock() tms.saveToDB() tms.mtx.Unlock() - case <-tms.stop: + case <-tms.Quit: break loop } } @@ -211,11 +216,22 @@ loop: //--------------------------------------------------------------------------------------- -// The number of event updates that can be sent on a single metric before blocking -const defaultUpdateChanCapacity = 10 +const ( + // The number of event updates that can be sent on a single metric before blocking + defaultUpdateChanCapacity = 10 + + // The number of trust value requests that can be made simultaneously before blocking + defaultRequestChanCapacity = 10 -// The number of trust value requests that can be made simultaneously before blocking -const defaultRequestChanCapacity = 10 + // The weight applied to the derivative when current behavior is >= previous behavior + defaultDerivativeGamma1 = 0 + + // The weight applied to the derivative when current behavior is less than previous behavior + defaultDerivativeGamma2 = 1.0 + + // The weight applied to history data values when calculating the history value + defaultHistoryDataWeight = 0.8 +) // TrustMetric - keeps track of peer reliability // See tendermint/docs/architecture/adr-006-trust-metric.md for details @@ -238,6 +254,12 @@ type TrustMetric struct { // Stores the trust history data for this metric history []float64 + // Weights applied to the history data when calculating the history value + historyWeights []float64 + + // The sum of the history weights used when calculating the history value + historyWeightSum float64 + // The current number of history data elements historySize int @@ -286,23 +308,13 @@ func (tm *TrustMetric) Stop() { tm.stop <- true } -// BadEvent indicates that an undesirable event took place -func (tm *TrustMetric) BadEvent() { - tm.update <- &updateBadGood{IsBad: true, Add: 1} -} - -// AddBadEvents acknowledges multiple undesirable events -func (tm *TrustMetric) AddBadEvents(num int) { +// BadEvents indicates that an undesirable event(s) took place +func (tm *TrustMetric) BadEvents(num int) { tm.update <- &updateBadGood{IsBad: true, Add: num} } -// GoodEvent indicates that a desirable event took place -func (tm *TrustMetric) GoodEvent() { - tm.update <- &updateBadGood{IsBad: false, Add: 1} -} - -// AddGoodEvents acknowledges multiple desirable events -func (tm *TrustMetric) AddGoodEvents(num int) { +// GoodEvents indicates that a desirable event(s) took place +func (tm *TrustMetric) GoodEvents(num int) { tm.update <- &updateBadGood{IsBad: false, Add: num} } @@ -316,10 +328,9 @@ func (tm *TrustMetric) TrustValue() float64 { // TrustScore gets a score based on the trust value always between 0 and 100 func (tm *TrustMetric) TrustScore() int { - resp := make(chan float64, 1) + score := tm.TrustValue() * 100 - tm.trustValue <- &reqTrustValue{Resp: resp} - return int(math.Floor(<-resp * 100)) + return int(math.Floor(score)) } // TrustMetricConfig - Configures the weight functions and time intervals for the metric @@ -373,7 +384,7 @@ func NewMetricWithConfig(tmc TrustMetricConfig) *TrustMetric { // Setup the channels tm.update = make(chan *updateBadGood, defaultUpdateChanCapacity) tm.trustValue = make(chan *reqTrustValue, defaultRequestChanCapacity) - tm.stop = make(chan bool, 2) + tm.stop = make(chan bool, 1) go tm.processRequests() return tm @@ -413,12 +424,11 @@ func (tm *TrustMetric) derivativeValue() float64 { // Strengthens the derivative component when the change is negative func (tm *TrustMetric) weightedDerivative() float64 { - var weight float64 + var weight float64 = defaultDerivativeGamma1 d := tm.derivativeValue() - if d < 0 { - weight = 1.0 + weight = defaultDerivativeGamma2 } return weight * d } @@ -431,9 +441,10 @@ func (tm *TrustMetric) updateFadedMemory() { return } - first := tm.historySize - 1 + end := tm.historySize - 1 // Keep the most recent history element - for count, i := 1, first-1; count < tm.historySize; count, i = count+1, i-1 { + for count := 1; count < tm.historySize; count++ { + i := end - count // The older the data is, the more we spread it out x := math.Pow(2, float64(count)) // Two history data values are merged into a single value @@ -443,7 +454,10 @@ func (tm *TrustMetric) updateFadedMemory() { // Map the interval value down to an offset from the beginning of history func intervalToHistoryOffset(interval int) int { - return int(math.Floor(math.Log(float64(interval)) / math.Log(2))) + // The system maintains 2^m interval values in the form of m history + // data values. Therefore, we access the ith interval by obtaining + // the history data index = the floor of log2(i) + return int(math.Floor(math.Log2(float64(interval)))) } // Retrieves the actual history data value that represents the requested time interval @@ -461,37 +475,21 @@ func (tm *TrustMetric) fadedMemoryValue(interval int) float64 { // Calculates the integral (history) component of the trust value func (tm *TrustMetric) calcHistoryValue() float64 { - var wk []float64 - - // Create the weights. - hlen := tm.numIntervals - for i := 0; i < hlen; i++ { - x := math.Pow(.8, float64(i+1)) // Optimistic weight - wk = append(wk, x) - } + var hv float64 - var wsum float64 - // Calculate the sum of the weights - for _, v := range wk { - wsum += v + for i := 0; i < tm.numIntervals; i++ { + hv += tm.fadedMemoryValue(i) * tm.historyWeights[i] } - var hv float64 - // Calculate the history value - for i := 0; i < hlen; i++ { - weight := wk[i] / wsum - hv += tm.fadedMemoryValue(i) * weight - } - return hv + return hv / tm.historyWeightSum } // Calculates the current score for good/bad experiences func (tm *TrustMetric) proportionalValue() float64 { value := 1.0 - // Bad events are worth more in the calculation of our score - total := tm.good + math.Pow(tm.bad, 2) - if tm.bad > 0 || tm.good > 0 { + total := tm.good + tm.bad + if total > 0 { value = tm.good / total } return value @@ -545,14 +543,17 @@ loop: if tm.historySize < tm.historyMaxSize { tm.historySize++ } else { - last := len(tm.history) - tm.historyMaxSize - // Keep the history no larger than historyMaxSize + last := len(tm.history) - tm.historyMaxSize tm.history = tm.history[last:] } if tm.numIntervals < tm.maxIntervals { tm.numIntervals++ + // Add the optimistic weight for the new time interval + wk := math.Pow(defaultHistoryDataWeight, float64(tm.numIntervals)) + tm.historyWeights = append(tm.historyWeights, wk) + tm.historyWeightSum += wk } // Update the history data using Faded Memories diff --git a/p2p/trust/trustmetric_test.go b/p2p/trust/trustmetric_test.go index 626ca3bd5..56441c721 100644 --- a/p2p/trust/trustmetric_test.go +++ b/p2p/trust/trustmetric_test.go @@ -45,8 +45,8 @@ func TestTrustMetricStoreSaveLoad(t *testing.T) { key := fmt.Sprintf("peer_%d", i) tm := store.GetPeerTrustMetric(key) - tm.AddBadEvents(10) - tm.GoodEvent() + tm.BadEvents(10) + tm.GoodEvents(1) } // Check that we have 100 entries and save @@ -134,10 +134,10 @@ func TestTrustMetricStorePeerScore(t *testing.T) { assert.Equal(t, 100, first) // Add some undesirable events and disconnect - tm.BadEvent() + tm.BadEvents(1) first = tm.TrustScore() assert.NotEqual(t, 100, first) - tm.AddBadEvents(10) + tm.BadEvents(10) second := tm.TrustScore() if second > first { @@ -155,12 +155,12 @@ func TestTrustMetricScores(t *testing.T) { tm := NewMetric() // Perfect score - tm.GoodEvent() + tm.GoodEvents(1) score := tm.TrustScore() assert.Equal(t, 100, score) // Less than perfect score - tm.AddBadEvents(10) + tm.BadEvents(10) score = tm.TrustScore() assert.NotEqual(t, 100, score) tm.Stop() @@ -216,7 +216,7 @@ func TestTrustMetricStopPause(t *testing.T) { assert.Equal(t, first, tm.numIntervals) // Get the trust metric activated again - tm.AddGoodEvents(5) + tm.GoodEvents(5) // Allow some time intervals to pass and stop time.Sleep(50 * time.Millisecond) tm.Stop()