Browse Source

Merge pull request #917 from tendermint/trust-metric-cleanup

Trust metric cleanup
pull/927/head
Ethan Buchman 7 years ago
committed by GitHub
parent
commit
58b4a8395b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 436 additions and 414 deletions
  1. +56
    -0
      p2p/trust/config.go
  2. +98
    -332
      p2p/trust/metric.go
  3. +90
    -0
      p2p/trust/metric_test.go
  4. +192
    -0
      p2p/trust/store.go
  5. +0
    -82
      p2p/trust/store_test.go

+ 56
- 0
p2p/trust/config.go View File

@ -0,0 +1,56 @@
package trust
import "time"
// TrustMetricConfig - Configures the weight functions and time intervals for the metric
type TrustMetricConfig struct {
// Determines the percentage given to current behavior
ProportionalWeight float64
// Determines the percentage given to prior behavior
IntegralWeight float64
// The window of time that the trust metric will track events across.
// This can be set to cover many days without issue
TrackingWindow time.Duration
// Each interval should be short for adapability.
// Less than 30 seconds is too sensitive,
// and greater than 5 minutes will make the metric numb
IntervalLength time.Duration
}
// DefaultConfig returns a config with values that have been tested and produce desirable results
func DefaultConfig() TrustMetricConfig {
return TrustMetricConfig{
ProportionalWeight: 0.4,
IntegralWeight: 0.6,
TrackingWindow: (time.Minute * 60 * 24) * 14, // 14 days.
IntervalLength: 1 * time.Minute,
}
}
// Ensures that all configuration elements have valid values
func customConfig(tmc TrustMetricConfig) TrustMetricConfig {
config := DefaultConfig()
// Check the config for set values, and setup appropriately
if tmc.ProportionalWeight > 0 {
config.ProportionalWeight = tmc.ProportionalWeight
}
if tmc.IntegralWeight > 0 {
config.IntegralWeight = tmc.IntegralWeight
}
if tmc.IntervalLength > time.Duration(0) {
config.IntervalLength = tmc.IntervalLength
}
if tmc.TrackingWindow > time.Duration(0) &&
tmc.TrackingWindow >= config.IntervalLength {
config.TrackingWindow = tmc.TrackingWindow
}
return config
}

p2p/trust/trustmetric.go → p2p/trust/metric.go View File


+ 90
- 0
p2p/trust/metric_test.go View File

@ -0,0 +1,90 @@
package trust
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTrustMetricScores(t *testing.T) {
tm := NewMetric()
// Perfect score
tm.GoodEvents(1)
score := tm.TrustScore()
assert.Equal(t, 100, score)
// Less than perfect score
tm.BadEvents(10)
score = tm.TrustScore()
assert.NotEqual(t, 100, score)
tm.Stop()
}
func TestTrustMetricConfig(t *testing.T) {
// 7 days
window := time.Minute * 60 * 24 * 7
config := TrustMetricConfig{
TrackingWindow: window,
IntervalLength: 2 * time.Minute,
}
tm := NewMetricWithConfig(config)
// The max time intervals should be the TrackingWindow / IntervalLen
assert.Equal(t, int(config.TrackingWindow/config.IntervalLength), tm.maxIntervals)
dc := DefaultConfig()
// These weights should still be the default values
assert.Equal(t, dc.ProportionalWeight, tm.proportionalWeight)
assert.Equal(t, dc.IntegralWeight, tm.integralWeight)
tm.Stop()
config.ProportionalWeight = 0.3
config.IntegralWeight = 0.7
tm = NewMetricWithConfig(config)
// These weights should be equal to our custom values
assert.Equal(t, config.ProportionalWeight, tm.proportionalWeight)
assert.Equal(t, config.IntegralWeight, tm.integralWeight)
tm.Stop()
}
func TestTrustMetricStopPause(t *testing.T) {
// Cause time intervals to pass quickly
config := TrustMetricConfig{
TrackingWindow: 5 * time.Minute,
IntervalLength: 10 * time.Millisecond,
}
tm := NewMetricWithConfig(config)
// Allow some time intervals to pass and pause
time.Sleep(50 * time.Millisecond)
tm.Pause()
// Give the pause some time to take place
time.Sleep(10 * time.Millisecond)
first := tm.Copy().numIntervals
// Allow more time to pass and check the intervals are unchanged
time.Sleep(50 * time.Millisecond)
assert.Equal(t, first, tm.numIntervals)
// Get the trust metric activated again
tm.GoodEvents(5)
// Allow some time intervals to pass and stop
time.Sleep(50 * time.Millisecond)
tm.Stop()
// Give the stop some time to take place
time.Sleep(10 * time.Millisecond)
second := tm.Copy().numIntervals
// Allow more time to pass and check the intervals are unchanged
time.Sleep(50 * time.Millisecond)
assert.Equal(t, second, tm.numIntervals)
if first >= second {
t.Fatalf("numIntervals should always increase or stay the same over time")
}
}

+ 192
- 0
p2p/trust/store.go View File

@ -0,0 +1,192 @@
// Copyright 2017 Tendermint. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package trust
import (
"encoding/json"
"sync"
"time"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
)
const defaultStorePeriodicSaveInterval = 1 * time.Minute
var trustMetricKey = []byte("trustMetricStore")
// TrustMetricStore - Manages all trust metrics for peers
type TrustMetricStore struct {
cmn.BaseService
// Maps a Peer.Key to that peer's TrustMetric
peerMetrics map[string]*TrustMetric
// Mutex that protects the map and history data file
mtx sync.Mutex
// The db where peer trust metric history data will be stored
db dbm.DB
// This configuration will be used when creating new TrustMetrics
config TrustMetricConfig
}
// NewTrustMetricStore returns a store that saves data to the DB
// and uses the config when creating new trust metrics
func NewTrustMetricStore(db dbm.DB, tmc TrustMetricConfig) *TrustMetricStore {
tms := &TrustMetricStore{
peerMetrics: make(map[string]*TrustMetric),
db: db,
config: tmc,
}
tms.BaseService = *cmn.NewBaseService(nil, "TrustMetricStore", tms)
return tms
}
// OnStart implements Service
func (tms *TrustMetricStore) OnStart() error {
if err := tms.BaseService.OnStart(); err != nil {
return err
}
tms.mtx.Lock()
defer tms.mtx.Unlock()
tms.loadFromDB()
go tms.saveRoutine()
return nil
}
// OnStop implements Service
func (tms *TrustMetricStore) OnStop() {
tms.BaseService.OnStop()
tms.mtx.Lock()
defer tms.mtx.Unlock()
// Stop all trust metric go-routines
for _, tm := range tms.peerMetrics {
tm.Stop()
}
// Make the final trust history data save
tms.saveToDB()
}
// Size returns the number of entries in the trust metric store
func (tms *TrustMetricStore) Size() int {
tms.mtx.Lock()
defer tms.mtx.Unlock()
return tms.size()
}
// GetPeerTrustMetric returns a trust metric by peer key
func (tms *TrustMetricStore) GetPeerTrustMetric(key string) *TrustMetric {
tms.mtx.Lock()
defer tms.mtx.Unlock()
tm, ok := tms.peerMetrics[key]
if !ok {
// If the metric is not available, we will create it
tm = NewMetricWithConfig(tms.config)
// The metric needs to be in the map
tms.peerMetrics[key] = tm
}
return tm
}
// PeerDisconnected pauses the trust metric associated with the peer identified by the key
func (tms *TrustMetricStore) PeerDisconnected(key string) {
tms.mtx.Lock()
defer tms.mtx.Unlock()
// If the Peer that disconnected has a metric, pause it
if tm, ok := tms.peerMetrics[key]; ok {
tm.Pause()
}
}
// Saves the history data for all peers to the store DB.
// This public method acquires the trust metric store lock
func (tms *TrustMetricStore) SaveToDB() {
tms.mtx.Lock()
defer tms.mtx.Unlock()
tms.saveToDB()
}
/* Private methods */
// size returns the number of entries in the store without acquiring the mutex
func (tms *TrustMetricStore) size() int {
return len(tms.peerMetrics)
}
/* Loading & Saving */
/* Both loadFromDB and savetoDB assume the mutex has been acquired */
// 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
bytes := tms.db.Get(trustMetricKey)
if bytes == nil {
return false
}
peers := make(map[string]MetricHistoryJSON, 0)
err := json.Unmarshal(bytes, &peers)
if err != nil {
cmn.PanicCrisis(cmn.Fmt("Could not unmarshal Trust Metric Store DB data: %v", err))
}
// If history data exists in the file,
// load it into trust metric
for key, p := range peers {
tm := NewMetricWithConfig(tms.config)
tm.Init(p)
// Load the peer trust metric into the store
tms.peerMetrics[key] = tm
}
return true
}
// Saves the history data for all peers to the store DB
func (tms *TrustMetricStore) saveToDB() {
tms.Logger.Debug("Saving TrustHistory to DB", "size", tms.size())
peers := make(map[string]MetricHistoryJSON, 0)
for key, tm := range tms.peerMetrics {
// Add an entry for the peer identified by key
peers[key] = tm.HistoryJSON()
}
// Write all the data back to the DB
bytes, err := json.Marshal(peers)
if err != nil {
tms.Logger.Error("Failed to encode the TrustHistory", "err", err)
return
}
tms.db.SetSync(trustMetricKey, bytes)
}
// Periodically saves the trust history data to the DB
func (tms *TrustMetricStore) saveRoutine() {
t := time.NewTicker(defaultStorePeriodicSaveInterval)
defer t.Stop()
loop:
for {
select {
case <-t.C:
tms.SaveToDB()
case <-tms.Quit:
break loop
}
}
}

p2p/trust/trustmetric_test.go → p2p/trust/store_test.go View File


Loading…
Cancel
Save