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  }