// 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" "fmt" "sync" "time" "github.com/tendermint/tendermint/libs/service" dbm "github.com/tendermint/tm-db" ) const defaultStorePeriodicSaveInterval = 1 * time.Minute var trustMetricKey = []byte("trustMetricStore") // MetricStore - Manages all trust metrics for peers type MetricStore struct { service.BaseService // Maps a Peer.Key to that peer's TrustMetric peerMetrics map[string]*Metric // 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 MetricConfig } // NewTrustMetricStore returns a store that saves data to the DB // and uses the config when creating new trust metrics. // Use Start to to initialize the trust metric store func NewTrustMetricStore(db dbm.DB, tmc MetricConfig) *MetricStore { tms := &MetricStore{ peerMetrics: make(map[string]*Metric), db: db, config: tmc, } tms.BaseService = *service.NewBaseService(nil, "MetricStore", tms) return tms } // OnStart implements Service func (tms *MetricStore) 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 *MetricStore) 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 *MetricStore) Size() int { tms.mtx.Lock() defer tms.mtx.Unlock() return tms.size() } // AddPeerTrustMetric takes an existing trust metric and associates it with a peer key. // The caller is expected to call Start on the TrustMetric being added func (tms *MetricStore) AddPeerTrustMetric(key string, tm *Metric) { tms.mtx.Lock() defer tms.mtx.Unlock() if key == "" || tm == nil { return } tms.peerMetrics[key] = tm } // GetPeerTrustMetric returns a trust metric by peer key func (tms *MetricStore) GetPeerTrustMetric(key string) *Metric { 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) tm.Start() // 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 *MetricStore) 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 *MetricStore) 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 *MetricStore) 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 *MetricStore) 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) err := json.Unmarshal(bytes, &peers) if err != nil { panic(fmt.Sprintf("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.Start() 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 *MetricStore) saveToDB() { tms.Logger.Debug("Saving TrustHistory to DB", "size", tms.size()) peers := make(map[string]MetricHistoryJSON) 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 *MetricStore) saveRoutine() { t := time.NewTicker(defaultStorePeriodicSaveInterval) defer t.Stop() loop: for { select { case <-t.C: tms.SaveToDB() case <-tms.Quit(): break loop } } }