github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/p2p/trust/store.go (about)

     1  package trust
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"time"
     7  
     8  	dbm "github.com/cometbft/cometbft-db"
     9  
    10  	"github.com/badrootd/celestia-core/libs/service"
    11  	cmtsync "github.com/badrootd/celestia-core/libs/sync"
    12  )
    13  
    14  const defaultStorePeriodicSaveInterval = 1 * time.Minute
    15  
    16  var trustMetricKey = []byte("trustMetricStore")
    17  
    18  // MetricStore - Manages all trust metrics for peers
    19  type MetricStore struct {
    20  	service.BaseService
    21  
    22  	// Maps a Peer.Key to that peer's TrustMetric
    23  	peerMetrics map[string]*Metric
    24  
    25  	// Mutex that protects the map and history data file
    26  	mtx cmtsync.Mutex
    27  
    28  	// The db where peer trust metric history data will be stored
    29  	db dbm.DB
    30  
    31  	// This configuration will be used when creating new TrustMetrics
    32  	config MetricConfig
    33  }
    34  
    35  // NewTrustMetricStore returns a store that saves data to the DB
    36  // and uses the config when creating new trust metrics.
    37  // Use Start to to initialize the trust metric store
    38  func NewTrustMetricStore(db dbm.DB, tmc MetricConfig) *MetricStore {
    39  	tms := &MetricStore{
    40  		peerMetrics: make(map[string]*Metric),
    41  		db:          db,
    42  		config:      tmc,
    43  	}
    44  
    45  	tms.BaseService = *service.NewBaseService(nil, "MetricStore", tms)
    46  	return tms
    47  }
    48  
    49  // OnStart implements Service
    50  func (tms *MetricStore) OnStart() error {
    51  	if err := tms.BaseService.OnStart(); err != nil {
    52  		return err
    53  	}
    54  
    55  	tms.mtx.Lock()
    56  	defer tms.mtx.Unlock()
    57  
    58  	tms.loadFromDB()
    59  	go tms.saveRoutine()
    60  	return nil
    61  }
    62  
    63  // OnStop implements Service
    64  func (tms *MetricStore) OnStop() {
    65  	tms.BaseService.OnStop()
    66  
    67  	tms.mtx.Lock()
    68  	defer tms.mtx.Unlock()
    69  
    70  	// Stop all trust metric go-routines
    71  	for _, tm := range tms.peerMetrics {
    72  		if err := tm.Stop(); err != nil {
    73  			tms.Logger.Error("unable to stop metric store", "error", err)
    74  		}
    75  	}
    76  
    77  	// Make the final trust history data save
    78  	tms.saveToDB()
    79  }
    80  
    81  // Size returns the number of entries in the trust metric store
    82  func (tms *MetricStore) Size() int {
    83  	tms.mtx.Lock()
    84  	defer tms.mtx.Unlock()
    85  
    86  	return tms.size()
    87  }
    88  
    89  // AddPeerTrustMetric takes an existing trust metric and associates it with a peer key.
    90  // The caller is expected to call Start on the TrustMetric being added
    91  func (tms *MetricStore) AddPeerTrustMetric(key string, tm *Metric) {
    92  	tms.mtx.Lock()
    93  	defer tms.mtx.Unlock()
    94  
    95  	if key == "" || tm == nil {
    96  		return
    97  	}
    98  	tms.peerMetrics[key] = tm
    99  }
   100  
   101  // GetPeerTrustMetric returns a trust metric by peer key
   102  func (tms *MetricStore) GetPeerTrustMetric(key string) *Metric {
   103  	tms.mtx.Lock()
   104  	defer tms.mtx.Unlock()
   105  
   106  	tm, ok := tms.peerMetrics[key]
   107  	if !ok {
   108  		// If the metric is not available, we will create it
   109  		tm = NewMetricWithConfig(tms.config)
   110  		if err := tm.Start(); err != nil {
   111  			tms.Logger.Error("unable to start metric store", "error", err)
   112  		}
   113  		// The metric needs to be in the map
   114  		tms.peerMetrics[key] = tm
   115  	}
   116  	return tm
   117  }
   118  
   119  // PeerDisconnected pauses the trust metric associated with the peer identified by the key
   120  func (tms *MetricStore) PeerDisconnected(key string) {
   121  	tms.mtx.Lock()
   122  	defer tms.mtx.Unlock()
   123  
   124  	// If the Peer that disconnected has a metric, pause it
   125  	if tm, ok := tms.peerMetrics[key]; ok {
   126  		tm.Pause()
   127  	}
   128  }
   129  
   130  // Saves the history data for all peers to the store DB.
   131  // This public method acquires the trust metric store lock
   132  func (tms *MetricStore) SaveToDB() {
   133  	tms.mtx.Lock()
   134  	defer tms.mtx.Unlock()
   135  
   136  	tms.saveToDB()
   137  }
   138  
   139  /* Private methods */
   140  
   141  // size returns the number of entries in the store without acquiring the mutex
   142  func (tms *MetricStore) size() int {
   143  	return len(tms.peerMetrics)
   144  }
   145  
   146  /* Loading & Saving */
   147  /* Both loadFromDB and savetoDB assume the mutex has been acquired */
   148  
   149  // Loads the history data for all peers from the store DB
   150  // cmn.Panics if file is corrupt
   151  func (tms *MetricStore) loadFromDB() bool {
   152  	// Obtain the history data we have so far
   153  	bytes, err := tms.db.Get(trustMetricKey)
   154  	if err != nil {
   155  		panic(err)
   156  	}
   157  	if bytes == nil {
   158  		return false
   159  	}
   160  
   161  	peers := make(map[string]MetricHistoryJSON)
   162  	err = json.Unmarshal(bytes, &peers)
   163  	if err != nil {
   164  		panic(fmt.Sprintf("Could not unmarshal Trust Metric Store DB data: %v", err))
   165  	}
   166  
   167  	// If history data exists in the file,
   168  	// load it into trust metric
   169  	for key, p := range peers {
   170  		tm := NewMetricWithConfig(tms.config)
   171  
   172  		if err := tm.Start(); err != nil {
   173  			tms.Logger.Error("unable to start metric", "error", err)
   174  		}
   175  		tm.Init(p)
   176  		// Load the peer trust metric into the store
   177  		tms.peerMetrics[key] = tm
   178  	}
   179  	return true
   180  }
   181  
   182  // Saves the history data for all peers to the store DB
   183  func (tms *MetricStore) saveToDB() {
   184  	tms.Logger.Debug("Saving TrustHistory to DB", "size", tms.size())
   185  
   186  	peers := make(map[string]MetricHistoryJSON)
   187  
   188  	for key, tm := range tms.peerMetrics {
   189  		// Add an entry for the peer identified by key
   190  		peers[key] = tm.HistoryJSON()
   191  	}
   192  
   193  	// Write all the data back to the DB
   194  	bytes, err := json.Marshal(peers)
   195  	if err != nil {
   196  		tms.Logger.Error("Failed to encode the TrustHistory", "err", err)
   197  		return
   198  	}
   199  	if err := tms.db.SetSync(trustMetricKey, bytes); err != nil {
   200  		tms.Logger.Error("failed to flush data to disk", "error", err)
   201  	}
   202  }
   203  
   204  // Periodically saves the trust history data to the DB
   205  func (tms *MetricStore) saveRoutine() {
   206  	t := time.NewTicker(defaultStorePeriodicSaveInterval)
   207  	defer t.Stop()
   208  loop:
   209  	for {
   210  		select {
   211  		case <-t.C:
   212  			tms.SaveToDB()
   213  		case <-tms.Quit():
   214  			break loop
   215  		}
   216  	}
   217  }