github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowman/poll/early_term_no_traversal.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package poll 5 6 import ( 7 "errors" 8 "fmt" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 13 "github.com/MetalBlockchain/metalgo/ids" 14 "github.com/MetalBlockchain/metalgo/utils/bag" 15 ) 16 17 var ( 18 errPollDurationVectorMetrics = errors.New("failed to register poll_duration vector metrics") 19 errPollCountVectorMetrics = errors.New("failed to register poll_count vector metrics") 20 21 terminationReason = "reason" 22 exhaustedReason = "exhausted" 23 earlyFailReason = "early_fail" 24 earlyAlphaPrefReason = "early_alpha_pref" 25 earlyAlphaConfReason = "early_alpha_conf" 26 27 exhaustedLabel = prometheus.Labels{ 28 terminationReason: exhaustedReason, 29 } 30 earlyFailLabel = prometheus.Labels{ 31 terminationReason: earlyFailReason, 32 } 33 earlyAlphaPrefLabel = prometheus.Labels{ 34 terminationReason: earlyAlphaPrefReason, 35 } 36 earlyAlphaConfLabel = prometheus.Labels{ 37 terminationReason: earlyAlphaConfReason, 38 } 39 ) 40 41 type earlyTermNoTraversalMetrics struct { 42 durExhaustedPolls prometheus.Gauge 43 durEarlyFailPolls prometheus.Gauge 44 durEarlyAlphaPrefPolls prometheus.Gauge 45 durEarlyAlphaConfPolls prometheus.Gauge 46 47 countExhaustedPolls prometheus.Counter 48 countEarlyFailPolls prometheus.Counter 49 countEarlyAlphaPrefPolls prometheus.Counter 50 countEarlyAlphaConfPolls prometheus.Counter 51 } 52 53 func newEarlyTermNoTraversalMetrics(reg prometheus.Registerer) (*earlyTermNoTraversalMetrics, error) { 54 pollCountVec := prometheus.NewCounterVec(prometheus.CounterOpts{ 55 Name: "poll_count", 56 Help: "Total # of terminated polls by reason", 57 }, []string{terminationReason}) 58 if err := reg.Register(pollCountVec); err != nil { 59 return nil, fmt.Errorf("%w: %w", errPollCountVectorMetrics, err) 60 } 61 durPollsVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{ 62 Name: "poll_duration", 63 Help: "time (in ns) polls took to complete by reason", 64 }, []string{terminationReason}) 65 if err := reg.Register(durPollsVec); err != nil { 66 return nil, fmt.Errorf("%w: %w", errPollDurationVectorMetrics, err) 67 } 68 69 return &earlyTermNoTraversalMetrics{ 70 durExhaustedPolls: durPollsVec.With(exhaustedLabel), 71 durEarlyFailPolls: durPollsVec.With(earlyFailLabel), 72 durEarlyAlphaPrefPolls: durPollsVec.With(earlyAlphaPrefLabel), 73 durEarlyAlphaConfPolls: durPollsVec.With(earlyAlphaConfLabel), 74 countExhaustedPolls: pollCountVec.With(exhaustedLabel), 75 countEarlyFailPolls: pollCountVec.With(earlyFailLabel), 76 countEarlyAlphaPrefPolls: pollCountVec.With(earlyAlphaPrefLabel), 77 countEarlyAlphaConfPolls: pollCountVec.With(earlyAlphaConfLabel), 78 }, nil 79 } 80 81 func (m *earlyTermNoTraversalMetrics) observeExhausted(duration time.Duration) { 82 m.durExhaustedPolls.Add(float64(duration.Nanoseconds())) 83 m.countExhaustedPolls.Inc() 84 } 85 86 func (m *earlyTermNoTraversalMetrics) observeEarlyFail(duration time.Duration) { 87 m.durEarlyFailPolls.Add(float64(duration.Nanoseconds())) 88 m.countEarlyFailPolls.Inc() 89 } 90 91 func (m *earlyTermNoTraversalMetrics) observeEarlyAlphaPref(duration time.Duration) { 92 m.durEarlyAlphaPrefPolls.Add(float64(duration.Nanoseconds())) 93 m.countEarlyAlphaPrefPolls.Inc() 94 } 95 96 func (m *earlyTermNoTraversalMetrics) observeEarlyAlphaConf(duration time.Duration) { 97 m.durEarlyAlphaConfPolls.Add(float64(duration.Nanoseconds())) 98 m.countEarlyAlphaConfPolls.Inc() 99 } 100 101 type earlyTermNoTraversalFactory struct { 102 alphaPreference int 103 alphaConfidence int 104 105 metrics *earlyTermNoTraversalMetrics 106 } 107 108 // NewEarlyTermNoTraversalFactory returns a factory that returns polls with 109 // early termination, without doing DAG traversals 110 func NewEarlyTermNoTraversalFactory( 111 alphaPreference int, 112 alphaConfidence int, 113 reg prometheus.Registerer, 114 ) (Factory, error) { 115 metrics, err := newEarlyTermNoTraversalMetrics(reg) 116 if err != nil { 117 return nil, err 118 } 119 120 return &earlyTermNoTraversalFactory{ 121 alphaPreference: alphaPreference, 122 alphaConfidence: alphaConfidence, 123 metrics: metrics, 124 }, nil 125 } 126 127 func (f *earlyTermNoTraversalFactory) New(vdrs bag.Bag[ids.NodeID]) Poll { 128 return &earlyTermNoTraversalPoll{ 129 polled: vdrs, 130 alphaPreference: f.alphaPreference, 131 alphaConfidence: f.alphaConfidence, 132 metrics: f.metrics, 133 start: time.Now(), 134 } 135 } 136 137 // earlyTermNoTraversalPoll finishes when any remaining validators can't change 138 // the result of the poll. However, does not terminate tightly with this bound. 139 // It terminates as quickly as it can without performing any DAG traversals. 140 type earlyTermNoTraversalPoll struct { 141 votes bag.Bag[ids.ID] 142 polled bag.Bag[ids.NodeID] 143 alphaPreference int 144 alphaConfidence int 145 146 metrics *earlyTermNoTraversalMetrics 147 start time.Time 148 finished bool 149 } 150 151 // Vote registers a response for this poll 152 func (p *earlyTermNoTraversalPoll) Vote(vdr ids.NodeID, vote ids.ID) { 153 count := p.polled.Count(vdr) 154 // make sure that a validator can't respond multiple times 155 p.polled.Remove(vdr) 156 157 // track the votes the validator responded with 158 p.votes.AddCount(vote, count) 159 } 160 161 // Drop any future response for this poll 162 func (p *earlyTermNoTraversalPoll) Drop(vdr ids.NodeID) { 163 p.polled.Remove(vdr) 164 } 165 166 // Finished returns true when one of the following conditions is met. 167 // 168 // 1. There are no outstanding votes. 169 // 2. It is impossible for the poll to achieve an alphaPreference majority 170 // after applying transitive voting. 171 // 3. A single element has achieved an alphaPreference majority and it is 172 // impossible for it to achieve an alphaConfidence majority after applying 173 // transitive voting. 174 // 4. A single element has achieved an alphaConfidence majority. 175 func (p *earlyTermNoTraversalPoll) Finished() bool { 176 if p.finished { 177 return true 178 } 179 180 remaining := p.polled.Len() 181 if remaining == 0 { 182 p.finished = true 183 p.metrics.observeExhausted(time.Since(p.start)) 184 return true // Case 1 185 } 186 187 received := p.votes.Len() 188 maxPossibleVotes := received + remaining 189 if maxPossibleVotes < p.alphaPreference { 190 p.finished = true 191 p.metrics.observeEarlyFail(time.Since(p.start)) 192 return true // Case 2 193 } 194 195 _, freq := p.votes.Mode() 196 if freq >= p.alphaPreference && maxPossibleVotes < p.alphaConfidence { 197 p.finished = true 198 p.metrics.observeEarlyAlphaPref(time.Since(p.start)) 199 return true // Case 3 200 } 201 202 if freq >= p.alphaConfidence { 203 p.finished = true 204 p.metrics.observeEarlyAlphaConf(time.Since(p.start)) 205 return true // Case 4 206 } 207 208 return false 209 } 210 211 // Result returns the result of this poll 212 func (p *earlyTermNoTraversalPoll) Result() bag.Bag[ids.ID] { 213 return p.votes 214 } 215 216 func (p *earlyTermNoTraversalPoll) PrefixedString(prefix string) string { 217 return fmt.Sprintf( 218 "waiting on %s\n%sreceived %s", 219 p.polled.PrefixedString(prefix), 220 prefix, 221 p.votes.PrefixedString(prefix), 222 ) 223 } 224 225 func (p *earlyTermNoTraversalPoll) String() string { 226 return p.PrefixedString("") 227 }