github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowman/poll/set.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  	"strings"
    10  	"time"
    11  
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"go.uber.org/zap"
    14  
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/utils/bag"
    17  	"github.com/MetalBlockchain/metalgo/utils/linked"
    18  	"github.com/MetalBlockchain/metalgo/utils/logging"
    19  	"github.com/MetalBlockchain/metalgo/utils/metric"
    20  )
    21  
    22  var (
    23  	errFailedPollsMetric         = errors.New("failed to register polls metric")
    24  	errFailedPollDurationMetrics = errors.New("failed to register poll_duration metrics")
    25  )
    26  
    27  type pollHolder interface {
    28  	GetPoll() Poll
    29  	StartTime() time.Time
    30  }
    31  
    32  type poll struct {
    33  	Poll
    34  	start time.Time
    35  }
    36  
    37  func (p poll) GetPoll() Poll {
    38  	return p
    39  }
    40  
    41  func (p poll) StartTime() time.Time {
    42  	return p.start
    43  }
    44  
    45  type set struct {
    46  	log      logging.Logger
    47  	numPolls prometheus.Gauge
    48  	durPolls metric.Averager
    49  	factory  Factory
    50  	// maps requestID -> poll
    51  	polls *linked.Hashmap[uint32, pollHolder]
    52  }
    53  
    54  // NewSet returns a new empty set of polls
    55  func NewSet(
    56  	factory Factory,
    57  	log logging.Logger,
    58  	reg prometheus.Registerer,
    59  ) (Set, error) {
    60  	numPolls := prometheus.NewGauge(prometheus.GaugeOpts{
    61  		Name: "polls",
    62  		Help: "Number of pending network polls",
    63  	})
    64  	if err := reg.Register(numPolls); err != nil {
    65  		return nil, fmt.Errorf("%w: %w", errFailedPollsMetric, err)
    66  	}
    67  
    68  	durPolls, err := metric.NewAverager(
    69  		"poll_duration",
    70  		"time (in ns) this poll took to complete",
    71  		reg,
    72  	)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("%w: %w", errFailedPollDurationMetrics, err)
    75  	}
    76  
    77  	return &set{
    78  		log:      log,
    79  		numPolls: numPolls,
    80  		durPolls: durPolls,
    81  		factory:  factory,
    82  		polls:    linked.NewHashmap[uint32, pollHolder](),
    83  	}, nil
    84  }
    85  
    86  // Add to the current set of polls
    87  // Returns true if the poll was registered correctly and the network sample
    88  // should be made.
    89  func (s *set) Add(requestID uint32, vdrs bag.Bag[ids.NodeID]) bool {
    90  	if _, exists := s.polls.Get(requestID); exists {
    91  		s.log.Debug("dropping poll",
    92  			zap.String("reason", "duplicated request"),
    93  			zap.Uint32("requestID", requestID),
    94  		)
    95  		return false
    96  	}
    97  
    98  	s.log.Verbo("creating poll",
    99  		zap.Uint32("requestID", requestID),
   100  		zap.Stringer("validators", &vdrs),
   101  	)
   102  
   103  	s.polls.Put(requestID, poll{
   104  		Poll:  s.factory.New(vdrs), // create the new poll
   105  		start: time.Now(),
   106  	})
   107  	s.numPolls.Inc() // increase the metrics
   108  	return true
   109  }
   110  
   111  // Vote registers the connections response to a query for [id]. If there was no
   112  // query, or the response has already be registered, nothing is performed.
   113  func (s *set) Vote(requestID uint32, vdr ids.NodeID, vote ids.ID) []bag.Bag[ids.ID] {
   114  	holder, exists := s.polls.Get(requestID)
   115  	if !exists {
   116  		s.log.Verbo("dropping vote",
   117  			zap.String("reason", "unknown poll"),
   118  			zap.Stringer("validator", vdr),
   119  			zap.Uint32("requestID", requestID),
   120  		)
   121  		return nil
   122  	}
   123  
   124  	p := holder.GetPoll()
   125  
   126  	s.log.Verbo("processing vote",
   127  		zap.Stringer("validator", vdr),
   128  		zap.Uint32("requestID", requestID),
   129  		zap.Stringer("vote", vote),
   130  	)
   131  
   132  	p.Vote(vdr, vote)
   133  	if !p.Finished() {
   134  		return nil
   135  	}
   136  
   137  	return s.processFinishedPolls()
   138  }
   139  
   140  // processFinishedPolls checks for other dependent finished polls and returns them all if finished
   141  func (s *set) processFinishedPolls() []bag.Bag[ids.ID] {
   142  	var results []bag.Bag[ids.ID]
   143  
   144  	// iterate from oldest to newest
   145  	iter := s.polls.NewIterator()
   146  	for iter.Next() {
   147  		holder := iter.Value()
   148  		p := holder.GetPoll()
   149  		if !p.Finished() {
   150  			// since we're iterating from oldest to newest, if the next poll has not finished,
   151  			// we can break and return what we have so far
   152  			break
   153  		}
   154  
   155  		s.log.Verbo("poll finished",
   156  			zap.Uint32("requestID", iter.Key()),
   157  			zap.Stringer("poll", holder.GetPoll()),
   158  		)
   159  		s.durPolls.Observe(float64(time.Since(holder.StartTime())))
   160  		s.numPolls.Dec() // decrease the metrics
   161  
   162  		results = append(results, p.Result())
   163  		s.polls.Delete(iter.Key())
   164  	}
   165  
   166  	// only gets here if the poll has finished
   167  	// results will have values if this and other newer polls have finished
   168  	return results
   169  }
   170  
   171  // Drop registers the connections response to a query for [id]. If there was no
   172  // query, or the response has already be registered, nothing is performed.
   173  func (s *set) Drop(requestID uint32, vdr ids.NodeID) []bag.Bag[ids.ID] {
   174  	holder, exists := s.polls.Get(requestID)
   175  	if !exists {
   176  		s.log.Verbo("dropping vote",
   177  			zap.String("reason", "unknown poll"),
   178  			zap.Stringer("validator", vdr),
   179  			zap.Uint32("requestID", requestID),
   180  		)
   181  		return nil
   182  	}
   183  
   184  	s.log.Verbo("processing dropped vote",
   185  		zap.Stringer("validator", vdr),
   186  		zap.Uint32("requestID", requestID),
   187  	)
   188  
   189  	poll := holder.GetPoll()
   190  
   191  	poll.Drop(vdr)
   192  	if !poll.Finished() {
   193  		return nil
   194  	}
   195  
   196  	return s.processFinishedPolls()
   197  }
   198  
   199  // Len returns the number of outstanding polls
   200  func (s *set) Len() int {
   201  	return s.polls.Len()
   202  }
   203  
   204  func (s *set) String() string {
   205  	sb := strings.Builder{}
   206  	sb.WriteString(fmt.Sprintf("current polls: (Size = %d)", s.polls.Len()))
   207  	iter := s.polls.NewIterator()
   208  	for iter.Next() {
   209  		requestID := iter.Key()
   210  		poll := iter.Value().(Poll)
   211  		sb.WriteString(fmt.Sprintf("\n    RequestID %d:\n        %s", requestID, poll.PrefixedString("        ")))
   212  	}
   213  	return sb.String()
   214  }