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 }