mirror of https://github.com/poanetwork/gecko.git
102 lines
3.2 KiB
Go
102 lines
3.2 KiB
Go
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||
|
// See the file LICENSE for licensing terms.
|
||
|
|
||
|
package avalanche
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
|
||
|
"github.com/ava-labs/gecko/ids"
|
||
|
"github.com/ava-labs/gecko/utils/logging"
|
||
|
)
|
||
|
|
||
|
// TODO: There is a conservative early termination case that doesn't require dag
|
||
|
// traversals we may want to implement. The algorithm would go as follows:
|
||
|
// Keep track of the number of response that reference an ID. If an ID gets >=
|
||
|
// alpha responses, then remove it from all responses and place it into a chit
|
||
|
// list. Remove all empty responses. If the number of responses + the number of
|
||
|
// pending responses is less than alpha, terminate the poll.
|
||
|
// In the synchronous + virtuous case, when everyone returns the same hash, the
|
||
|
// poll now terminates after receiving alpha responses.
|
||
|
// In the rogue case, it is possible that the poll doesn't terminate as quickly
|
||
|
// as possible, because IDs may have the alpha threshold but only when counting
|
||
|
// transitive votes. In this case, we may wait even if it is no longer possible
|
||
|
// for another ID to earn alpha votes.
|
||
|
// Because alpha is typically set close to k, this may not be performance
|
||
|
// critical. However, early termination may be performance critical with crashed
|
||
|
// nodes.
|
||
|
|
||
|
type polls struct {
|
||
|
log logging.Logger
|
||
|
numPolls prometheus.Gauge
|
||
|
m map[uint32]poll
|
||
|
}
|
||
|
|
||
|
// Add to the current set of polls
|
||
|
// Returns true if the poll was registered correctly and the network sample
|
||
|
// should be made.
|
||
|
func (p *polls) Add(requestID uint32, numPolled int) bool {
|
||
|
poll, exists := p.m[requestID]
|
||
|
if !exists {
|
||
|
poll.numPending = numPolled
|
||
|
p.m[requestID] = poll
|
||
|
|
||
|
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||
|
}
|
||
|
return !exists
|
||
|
}
|
||
|
|
||
|
// Vote registers the connections response to a query for [id]. If there was no
|
||
|
// query, or the response has already be registered, nothing is performed.
|
||
|
func (p *polls) Vote(requestID uint32, vdr ids.ShortID, votes []ids.ID) (ids.UniqueBag, bool) {
|
||
|
p.log.Verbo("Vote. requestID: %d. validatorID: %s.", requestID, vdr)
|
||
|
poll, exists := p.m[requestID]
|
||
|
p.log.Verbo("Poll: %+v", poll)
|
||
|
if !exists {
|
||
|
return nil, false
|
||
|
}
|
||
|
|
||
|
poll.Vote(votes)
|
||
|
if poll.Finished() {
|
||
|
p.log.Verbo("Poll is finished")
|
||
|
delete(p.m, requestID)
|
||
|
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||
|
return poll.votes, true
|
||
|
}
|
||
|
p.m[requestID] = poll
|
||
|
return nil, false
|
||
|
}
|
||
|
|
||
|
func (p *polls) String() string {
|
||
|
sb := strings.Builder{}
|
||
|
|
||
|
sb.WriteString(fmt.Sprintf("Current polls: (Size = %d)", len(p.m)))
|
||
|
for requestID, poll := range p.m {
|
||
|
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
|
||
|
}
|
||
|
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
// poll represents the current state of a network poll for a vertex
|
||
|
type poll struct {
|
||
|
votes ids.UniqueBag
|
||
|
numPending int
|
||
|
}
|
||
|
|
||
|
// Vote registers a vote for this poll
|
||
|
func (p *poll) Vote(votes []ids.ID) {
|
||
|
if p.numPending > 0 {
|
||
|
p.numPending--
|
||
|
p.votes.Add(uint(p.numPending), votes...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Finished returns true if the poll has completed, with no more required
|
||
|
// responses
|
||
|
func (p poll) Finished() bool { return p.numPending <= 0 }
|
||
|
func (p poll) String() string { return fmt.Sprintf("Waiting on %d chits", p.numPending) }
|