github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/committees/consensus_committee.go (about)

     1  package committees
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
     9  	"github.com/koko1123/flow-go-1/consensus/hotstuff/committees/leader"
    10  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    11  	"github.com/koko1123/flow-go-1/model/flow"
    12  	"github.com/koko1123/flow-go-1/model/flow/filter"
    13  	"github.com/koko1123/flow-go-1/state/protocol"
    14  	"github.com/koko1123/flow-go-1/state/protocol/seed"
    15  )
    16  
    17  var errSelectionNotComputed = fmt.Errorf("leader selection for epoch not yet computed")
    18  
    19  // Consensus represents the main committee for consensus nodes. The consensus
    20  // committee persists across epochs.
    21  type Consensus struct {
    22  	mu      sync.RWMutex
    23  	state   protocol.State                     // the protocol state
    24  	me      flow.Identifier                    // the node ID of this node
    25  	leaders map[uint64]*leader.LeaderSelection // pre-computed leader selection for each epoch
    26  }
    27  
    28  var _ hotstuff.Committee = (*Consensus)(nil)
    29  
    30  func NewConsensusCommittee(state protocol.State, me flow.Identifier) (*Consensus, error) {
    31  
    32  	com := &Consensus{
    33  		state:   state,
    34  		me:      me,
    35  		leaders: make(map[uint64]*leader.LeaderSelection),
    36  	}
    37  
    38  	final := state.Final()
    39  
    40  	// pre-compute leader selection for current epoch
    41  	current := final.Epochs().Current()
    42  	_, err := com.prepareLeaderSelection(current)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("could not add leader for current epoch: %w", err)
    45  	}
    46  
    47  	// Pre-compute leader selection for previous epoch, if it exists.
    48  	//
    49  	// This ensures we always know about leader selection for at least one full
    50  	// epoch into the past, ensuring we are able to not only determine the leader
    51  	// for block proposals we receive, but also adjudicate consensus-related
    52  	// challenges up to one epoch into the past.
    53  	previous := final.Epochs().Previous()
    54  	_, err = previous.Counter()
    55  	// if there is no previous epoch, return the committee as-is
    56  	if errors.Is(err, protocol.ErrNoPreviousEpoch) {
    57  		return com, nil
    58  	}
    59  	if err != nil {
    60  		return nil, fmt.Errorf("could not get previous epoch: %w", err)
    61  	}
    62  
    63  	_, err = com.prepareLeaderSelection(previous)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("could not add leader for previous epoch: %w", err)
    66  	}
    67  
    68  	return com, nil
    69  }
    70  
    71  // Identities returns the identities of all authorized consensus participants at the given block.
    72  // The order of the identities is the canonical order.
    73  func (c *Consensus) Identities(blockID flow.Identifier) (flow.IdentityList, error) {
    74  	il, err := c.state.AtBlockID(blockID).Identities(filter.IsVotingConsensusCommitteeMember)
    75  	return il, err
    76  }
    77  
    78  func (c *Consensus) Identity(blockID flow.Identifier, nodeID flow.Identifier) (*flow.Identity, error) {
    79  	identity, err := c.state.AtBlockID(blockID).Identity(nodeID)
    80  	if err != nil {
    81  		if protocol.IsIdentityNotFound(err) {
    82  			return nil, model.NewInvalidSignerErrorf("id %v is not a valid node id: %w", nodeID, err)
    83  		}
    84  		return nil, fmt.Errorf("could not get identity for node ID %x: %w", nodeID, err)
    85  	}
    86  	if !filter.IsVotingConsensusCommitteeMember(identity) {
    87  		return nil, model.NewInvalidSignerErrorf("node %v is not an authorized hotstuff voting participant", nodeID)
    88  	}
    89  	return identity, nil
    90  }
    91  
    92  // LeaderForView returns the node ID of the leader for the given view. Returns
    93  // the following errors:
    94  //   - epoch containing the requested view has not been set up (protocol.ErrNextEpochNotSetup)
    95  //   - epoch is too far in the past (leader.InvalidViewError)
    96  //   - any other error indicates an unexpected internal error
    97  func (c *Consensus) LeaderForView(view uint64) (flow.Identifier, error) {
    98  
    99  	// try to retrieve the leader from a pre-computed LeaderSelection
   100  	id, err := c.precomputedLeaderForView(view)
   101  	if err == nil {
   102  		return id, nil
   103  	}
   104  	if !errors.Is(err, errSelectionNotComputed) {
   105  		return flow.ZeroID, err
   106  	}
   107  	// we only reach the following code, if we got a errSelectionNotComputed
   108  
   109  	// STEP 2 - we haven't yet computed leader selection for an epoch containing
   110  	// the requested view. We compute leader selection for the current and previous
   111  	// epoch (w.r.t. the finalized head) at initialization then compute leader
   112  	// selection for the next epoch when we encounter any view for which we don't
   113  	// know the leader. The series of epochs we have computed leaders for is
   114  	// strictly consecutive, meaning we know the leader for all views V where:
   115  	//
   116  	//   oldestEpoch.firstView <= V <= newestEpoch.finalView
   117  	//
   118  	// Thus, the requested view is either before oldestEpoch.firstView or after
   119  	// newestEpoch.finalView.
   120  	//
   121  	// CASE 1: V < oldestEpoch.firstView
   122  	// If the view is before the first view we've computed the leader for, this
   123  	// represents an invalid query because we only guarantee the protocol state
   124  	// will contain epoch information for the current, previous, and next epoch -
   125  	// such a query must be for a view within an epoch at least TWO epochs before
   126  	// the current epoch when we started up. This is considered an invalid query.
   127  	//
   128  	// CASE 2: V > newestEpoch.finalView
   129  	// If the view is after the last view we've computed the leader for, we
   130  	// assume the view is within the next epoch (w.r.t. the finalized head).
   131  	// This assumption is equivalent to assuming that we build at least one
   132  	// block in every epoch, which is anyway a requirement for valid epochs.
   133  	//
   134  	epochs := c.state.Final().Epochs()
   135  	next := epochs.Next()
   136  
   137  	// TMP: EMERGENCY EPOCH CHAIN CONTINUATION [EECC]
   138  	//
   139  	// If we reach this code-path, it means we are about to propose or vote
   140  	// for the first block in the next epoch. If that epoch has not been
   141  	// committed or set up, rather than stopping consensus, this intervention
   142  	// will create a new fallback leader selection for the next epoch containing
   143  	// 6 months worth of views, so that consensus will have leaders specified
   144  	// for the duration of the current spork, without any epoch transitions.
   145  	//
   146  	_, err = next.DKG() // either of the following errors indicates that we have transitioned into EECC
   147  	if errors.Is(err, protocol.ErrEpochNotCommitted) || errors.Is(err, protocol.ErrNextEpochNotSetup) {
   148  		current := epochs.Current()
   149  
   150  		currentCounter, err := current.Counter()
   151  		if err != nil {
   152  			return flow.ZeroID, fmt.Errorf("could not get next epoch currentCounter: %w", err)
   153  		}
   154  		identities, err := current.InitialIdentities()
   155  		if err != nil {
   156  			return flow.ZeroID, fmt.Errorf("could not get epoch initial identities: %w", err)
   157  		}
   158  		// Get the random source
   159  		// CAUTION: this is re-using the same leader selection random source from the now-ending epoch
   160  		randomSeed, err := current.RandomSource()
   161  		if err != nil {
   162  			return flow.ZeroID, fmt.Errorf("could not get epoch seed: %w", err)
   163  		}
   164  		currentFinalView, err := current.FinalView()
   165  		if err != nil {
   166  			return flow.ZeroID, fmt.Errorf("could not get epoch first view: %w", err)
   167  		}
   168  
   169  		// we will inject a fallback leader selection in place of the next epoch
   170  		counter := currentCounter + 1
   171  		// the fallback leader selection begins after the final view of the current epoch
   172  		firstView := currentFinalView + 1
   173  
   174  		// create random number generator from the seed and customizer
   175  		rng, err := seed.PRGFromRandomSource(randomSeed, seed.ProtocolConsensusLeaderSelection)
   176  		if err != nil {
   177  			return flow.ZeroID, fmt.Errorf("could not create rng from seed: %w", err)
   178  		}
   179  
   180  		selection, err := leader.ComputeLeaderSelection(
   181  			firstView,
   182  			rng,
   183  			int(firstView+leader.EstimatedSixMonthOfViews), // the fallback epoch lasts until the next spork
   184  			identities.Filter(filter.IsVotingConsensusCommitteeMember),
   185  		)
   186  		if err != nil {
   187  			return flow.ZeroID, fmt.Errorf("could not compute epoch fallback leader selection: %w", err)
   188  		}
   189  		c.mu.Lock()
   190  		c.leaders[counter] = selection
   191  		c.mu.Unlock()
   192  		return selection.LeaderForView(view)
   193  	}
   194  	if err != nil {
   195  		return flow.ZeroID, fmt.Errorf("unexpected error in EECC logic while retrieving DKG data: %w", err)
   196  	}
   197  
   198  	// HAPPY PATH logic
   199  	selection, err := c.prepareLeaderSelection(next)
   200  	if err != nil {
   201  		return flow.ZeroID, fmt.Errorf("could not compute leader selection for next epoch: %w", err)
   202  	}
   203  
   204  	return selection.LeaderForView(view)
   205  }
   206  
   207  func (c *Consensus) Self() flow.Identifier {
   208  	return c.me
   209  }
   210  
   211  func (c *Consensus) DKG(blockID flow.Identifier) (hotstuff.DKG, error) {
   212  	return c.state.AtBlockID(blockID).Epochs().Current().DKG()
   213  }
   214  
   215  // precomputedLeaderForView retrieves the leader from the precomputed
   216  // LeaderSelection in `c.leaders`
   217  // Error returns:
   218  //   - errSelectionNotComputed [sentinel error] if there is no Epoch for view stored in `c.leaders`
   219  //   - unspecific error in case of unexpected problems and bugs
   220  func (c *Consensus) precomputedLeaderForView(view uint64) (flow.Identifier, error) {
   221  	c.mu.RLock()
   222  	defer c.mu.RUnlock()
   223  
   224  	// STEP 1 - look for an epoch matching this view for which we have already
   225  	// pre-computed leader selection. Epochs last ~500k views, so we find the
   226  	// epoch here 99.99% of the time. Since epochs are long-lived, it is fine
   227  	// for this to be linear in the number of epochs we have observed.
   228  	for _, selection := range c.leaders {
   229  
   230  		// try retrieving the leader
   231  		leaderID, err := selection.LeaderForView(view)
   232  		// if the view is out of range, try the next epoch
   233  		if leader.IsInvalidViewError(err) {
   234  			continue
   235  		}
   236  		if err != nil {
   237  			return flow.ZeroID, fmt.Errorf("could not get leader: %w", err)
   238  		}
   239  
   240  		return leaderID, nil
   241  	}
   242  
   243  	return flow.ZeroID, errSelectionNotComputed
   244  }
   245  
   246  // prepareLeaderSelection pre-computes and stores the leader selection for the
   247  // given epoch. Computing leader selection for the same epoch multiple times
   248  // is a no-op.
   249  //
   250  // Returns the leader selection for the given epoch.
   251  func (c *Consensus) prepareLeaderSelection(epoch protocol.Epoch) (*leader.LeaderSelection, error) {
   252  	c.mu.Lock()
   253  	defer c.mu.Unlock()
   254  
   255  	counter, err := epoch.Counter()
   256  	if err != nil {
   257  		return nil, fmt.Errorf("could not get counter for current epoch: %w", err)
   258  	}
   259  	// this is a no-op if we have already computed leaders for this epoch
   260  	selection, exists := c.leaders[counter]
   261  	if exists {
   262  		return selection, nil
   263  	}
   264  
   265  	selection, err = leader.SelectionForConsensus(epoch)
   266  	if err != nil {
   267  		return nil, fmt.Errorf("could not get leader selection for current epoch: %w", err)
   268  	}
   269  	c.leaders[counter] = selection
   270  
   271  	// now prune any old epochs, if we have exceeded our maximum of 3
   272  	// if we have fewer than 3 epochs, this is a no-op
   273  
   274  	// find the maximum counter, including the epoch we just computed
   275  	max := uint64(0)
   276  	for counter := range c.leaders {
   277  		if counter > max {
   278  			max = counter
   279  		}
   280  	}
   281  
   282  	// remove any epochs which aren't within the most recent 3
   283  	for counter := range c.leaders {
   284  		if counter+3 <= max {
   285  			delete(c.leaders, counter)
   286  		}
   287  	}
   288  
   289  	return selection, nil
   290  }