github.com/MetalBlockchain/metalgo@v1.11.9/snow/networking/benchlist/manager.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package benchlist
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/MetalBlockchain/metalgo/api/metrics"
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/snow"
    13  	"github.com/MetalBlockchain/metalgo/snow/validators"
    14  )
    15  
    16  var _ Manager = (*manager)(nil)
    17  
    18  // Manager provides an interface for a benchlist to register whether
    19  // queries have been successful or unsuccessful and place validators with
    20  // consistently failing queries on a benchlist to prevent waiting up to
    21  // the full network timeout for their responses.
    22  type Manager interface {
    23  	// RegisterResponse registers that we receive a request response from [nodeID]
    24  	// regarding [chainID] within the timeout
    25  	RegisterResponse(chainID ids.ID, nodeID ids.NodeID)
    26  	// RegisterFailure registers that a request to [nodeID] regarding
    27  	// [chainID] timed out
    28  	RegisterFailure(chainID ids.ID, nodeID ids.NodeID)
    29  	// RegisterChain registers a new chain with metrics under [namespace]
    30  	RegisterChain(ctx *snow.ConsensusContext) error
    31  	// IsBenched returns true if messages to [nodeID] regarding chain [chainID]
    32  	// should not be sent over the network and should immediately fail.
    33  	// Returns false if such messages should be sent, or if the chain is unknown.
    34  	IsBenched(nodeID ids.NodeID, chainID ids.ID) bool
    35  	// GetBenched returns an array of chainIDs where the specified
    36  	// [nodeID] is benched. If called on an id.ShortID that does
    37  	// not map to a validator, it will return an empty array.
    38  	GetBenched(nodeID ids.NodeID) []ids.ID
    39  }
    40  
    41  // Config defines the configuration for a benchlist
    42  type Config struct {
    43  	Benchable              Benchable             `json:"-"`
    44  	Validators             validators.Manager    `json:"-"`
    45  	BenchlistRegisterer    metrics.MultiGatherer `json:"-"`
    46  	Threshold              int                   `json:"threshold"`
    47  	MinimumFailingDuration time.Duration         `json:"minimumFailingDuration"`
    48  	Duration               time.Duration         `json:"duration"`
    49  	MaxPortion             float64               `json:"maxPortion"`
    50  }
    51  
    52  type manager struct {
    53  	config *Config
    54  	// Chain ID --> benchlist for that chain.
    55  	// Each benchlist is safe for concurrent access.
    56  	chainBenchlists map[ids.ID]Benchlist
    57  
    58  	lock sync.RWMutex
    59  }
    60  
    61  // NewManager returns a manager for chain-specific query benchlisting
    62  func NewManager(config *Config) Manager {
    63  	// If the maximum portion of validators allowed to be benchlisted
    64  	// is 0, return the no-op benchlist
    65  	if config.MaxPortion <= 0 {
    66  		return NewNoBenchlist()
    67  	}
    68  	return &manager{
    69  		config:          config,
    70  		chainBenchlists: make(map[ids.ID]Benchlist),
    71  	}
    72  }
    73  
    74  // IsBenched returns true if messages to [nodeID] regarding [chainID]
    75  // should not be sent over the network and should immediately fail.
    76  func (m *manager) IsBenched(nodeID ids.NodeID, chainID ids.ID) bool {
    77  	m.lock.RLock()
    78  	benchlist, exists := m.chainBenchlists[chainID]
    79  	m.lock.RUnlock()
    80  
    81  	if !exists {
    82  		return false
    83  	}
    84  	isBenched := benchlist.IsBenched(nodeID)
    85  	return isBenched
    86  }
    87  
    88  // GetBenched returns an array of chainIDs where the specified
    89  // [nodeID] is benched. If called on an id.ShortID that does
    90  // not map to a validator, it will return an empty array.
    91  func (m *manager) GetBenched(nodeID ids.NodeID) []ids.ID {
    92  	m.lock.RLock()
    93  	defer m.lock.RUnlock()
    94  
    95  	benched := []ids.ID{}
    96  	for chainID, benchlist := range m.chainBenchlists {
    97  		if !benchlist.IsBenched(nodeID) {
    98  			continue
    99  		}
   100  		benched = append(benched, chainID)
   101  	}
   102  	return benched
   103  }
   104  
   105  func (m *manager) RegisterChain(ctx *snow.ConsensusContext) error {
   106  	m.lock.Lock()
   107  	defer m.lock.Unlock()
   108  
   109  	if _, exists := m.chainBenchlists[ctx.ChainID]; exists {
   110  		return nil
   111  	}
   112  
   113  	reg, err := metrics.MakeAndRegister(
   114  		m.config.BenchlistRegisterer,
   115  		ctx.PrimaryAlias,
   116  	)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	benchlist, err := NewBenchlist(
   122  		ctx,
   123  		m.config.Benchable,
   124  		m.config.Validators,
   125  		m.config.Threshold,
   126  		m.config.MinimumFailingDuration,
   127  		m.config.Duration,
   128  		m.config.MaxPortion,
   129  		reg,
   130  	)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	m.chainBenchlists[ctx.ChainID] = benchlist
   136  	return nil
   137  }
   138  
   139  func (m *manager) RegisterResponse(chainID ids.ID, nodeID ids.NodeID) {
   140  	m.lock.RLock()
   141  	benchlist, exists := m.chainBenchlists[chainID]
   142  	m.lock.RUnlock()
   143  
   144  	if !exists {
   145  		return
   146  	}
   147  	benchlist.RegisterResponse(nodeID)
   148  }
   149  
   150  func (m *manager) RegisterFailure(chainID ids.ID, nodeID ids.NodeID) {
   151  	m.lock.RLock()
   152  	benchlist, exists := m.chainBenchlists[chainID]
   153  	m.lock.RUnlock()
   154  
   155  	if !exists {
   156  		return
   157  	}
   158  	benchlist.RegisterFailure(nodeID)
   159  }
   160  
   161  type noBenchlist struct{}
   162  
   163  // NewNoBenchlist returns an empty benchlist that will never stop any queries
   164  func NewNoBenchlist() Manager {
   165  	return &noBenchlist{}
   166  }
   167  
   168  func (noBenchlist) RegisterChain(*snow.ConsensusContext) error {
   169  	return nil
   170  }
   171  
   172  func (noBenchlist) RegisterResponse(ids.ID, ids.NodeID) {}
   173  
   174  func (noBenchlist) RegisterFailure(ids.ID, ids.NodeID) {}
   175  
   176  func (noBenchlist) IsBenched(ids.NodeID, ids.ID) bool {
   177  	return false
   178  }
   179  
   180  func (noBenchlist) GetBenched(ids.NodeID) []ids.ID {
   181  	return []ids.ID{}
   182  }