github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/cache/protocol_state_provider.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/libp2p/go-libp2p/core/peer"
     8  	"github.com/rs/zerolog"
     9  
    10  	"github.com/onflow/flow-go/model/flow"
    11  	"github.com/onflow/flow-go/model/flow/filter"
    12  	"github.com/onflow/flow-go/module"
    13  	"github.com/onflow/flow-go/network/p2p"
    14  	"github.com/onflow/flow-go/network/p2p/keyutils"
    15  	"github.com/onflow/flow-go/state/protocol"
    16  	"github.com/onflow/flow-go/state/protocol/events"
    17  )
    18  
    19  // ProtocolStateIDCache implements an `id.IdentityProvider` and `p2p.IDTranslator` for the set of
    20  // authorized Flow network participants as according to the given `protocol.State`.
    21  // the implementation assumes that the node information changes rarely, while queries are frequent.
    22  // Hence, we follow an event-driven design, where the ProtocolStateIDCache subscribes to relevant
    23  // protocol notifications (mainly Epoch notifications) and updates its internally cached list of
    24  // authorized node identities.
    25  // Note: this implementation is _eventually consistent_, where changes in the protocol state will
    26  // quickly, but not atomically, propagate to the ProtocolStateIDCache. This strongly benefits
    27  // performance and modularity, as we can cache identities locally here, while the marginal
    28  // delay of updates is of no concern to the protocol.
    29  type ProtocolStateIDCache struct {
    30  	events.Noop
    31  	identities flow.IdentityList
    32  	state      protocol.State
    33  	mu         sync.RWMutex
    34  	peerIDs    map[flow.Identifier]peer.ID
    35  	flowIDs    map[peer.ID]flow.Identifier
    36  	lookup     map[flow.Identifier]*flow.Identity
    37  	logger     zerolog.Logger
    38  }
    39  
    40  var _ module.IdentityProvider = (*ProtocolStateIDCache)(nil)
    41  var _ protocol.Consumer = (*ProtocolStateIDCache)(nil)
    42  var _ p2p.IDTranslator = (*ProtocolStateIDCache)(nil)
    43  
    44  func NewProtocolStateIDCache(
    45  	logger zerolog.Logger,
    46  	state protocol.State,
    47  	eventDistributor *events.Distributor,
    48  ) (*ProtocolStateIDCache, error) {
    49  	provider := &ProtocolStateIDCache{
    50  		state:  state,
    51  		logger: logger.With().Str("component", "protocol-state-id-cache").Logger(),
    52  	}
    53  
    54  	head, err := state.Final().Head()
    55  	if err != nil {
    56  		return nil, fmt.Errorf("failed to get latest state header: %w", err)
    57  	}
    58  
    59  	provider.update(head.ID())
    60  	eventDistributor.AddConsumer(provider)
    61  
    62  	return provider, nil
    63  }
    64  
    65  // EpochTransition is a callback function for notifying the `ProtocolStateIDCache`
    66  // of an Epoch transition that just occurred. Upon such notification, the internally-cached
    67  // Identity table of authorized network participants is updated.
    68  //
    69  // TODO: per API contract, implementations of `EpochTransition` should be non-blocking
    70  // and virtually latency free. However, we run data base queries and acquire locks here,
    71  // which is undesired.
    72  func (p *ProtocolStateIDCache) EpochTransition(newEpochCounter uint64, header *flow.Header) {
    73  	p.logger.Info().Uint64("newEpochCounter", newEpochCounter).Msg("epoch transition")
    74  	p.update(header.ID())
    75  }
    76  
    77  // EpochSetupPhaseStarted is a callback function for notifying the `ProtocolStateIDCache`
    78  // that the EpochSetup Phase has just stared. Upon such notification, the internally-cached
    79  // Identity table of authorized network participants is updated.
    80  //
    81  // TODO: per API contract, implementations of `EpochSetupPhaseStarted` should be non-blocking
    82  // and virtually latency free. However, we run data base queries and acquire locks here,
    83  // which is undesired.
    84  func (p *ProtocolStateIDCache) EpochSetupPhaseStarted(currentEpochCounter uint64, header *flow.Header) {
    85  	p.logger.Info().Uint64("currentEpochCounter", currentEpochCounter).Msg("epoch setup phase started")
    86  	p.update(header.ID())
    87  }
    88  
    89  // EpochCommittedPhaseStarted is a callback function for notifying the `ProtocolStateIDCache`
    90  // that the EpochCommitted Phase has just stared. Upon such notification, the internally-cached
    91  // Identity table of authorized network participants is updated.
    92  //
    93  // TODO: per API contract, implementations of `EpochCommittedPhaseStarted` should be non-blocking
    94  // and virtually latency free. However, we run data base queries and acquire locks here,
    95  // which is undesired.
    96  func (p *ProtocolStateIDCache) EpochCommittedPhaseStarted(currentEpochCounter uint64, header *flow.Header) {
    97  	p.logger.Info().Uint64("currentEpochCounter", currentEpochCounter).Msg("epoch committed phase started")
    98  	p.update(header.ID())
    99  }
   100  
   101  // update updates the cached identities stored in this provider.
   102  // This is called whenever an epoch event occurs, signaling a possible change in
   103  // protocol state identities.
   104  // Caution: this function is non-negligible latency (data base reads and acquiring locks). Therefore,
   105  // it is _not suitable_ to be executed by the publisher thread for protocol notifications.
   106  func (p *ProtocolStateIDCache) update(blockID flow.Identifier) {
   107  	p.logger.Info().Str("blockID", blockID.String()).Msg("updating cached identities")
   108  
   109  	identities, err := p.state.AtBlockID(blockID).Identities(filter.Any)
   110  	if err != nil {
   111  		// We don't want to continue with an expired identity list.
   112  		p.logger.Fatal().Err(err).Msg("failed to fetch new identities")
   113  	}
   114  
   115  	nIds := identities.Count()
   116  	peerIDs := make(map[flow.Identifier]peer.ID, nIds)
   117  	flowIDs := make(map[peer.ID]flow.Identifier, nIds)
   118  
   119  	for _, identity := range identities {
   120  		p.logger.Debug().Interface("identity", identity).Msg("extracting peer ID from network key")
   121  
   122  		pid, err := keyutils.PeerIDFromFlowPublicKey(identity.NetworkPubKey)
   123  		if err != nil {
   124  			p.logger.Err(err).Interface("identity", identity).Msg("failed to extract peer ID from network key")
   125  			continue
   126  		}
   127  
   128  		flowIDs[pid] = identity.NodeID
   129  		peerIDs[identity.NodeID] = pid
   130  	}
   131  
   132  	p.mu.Lock()
   133  	defer p.mu.Unlock()
   134  	p.identities = identities
   135  	p.flowIDs = flowIDs
   136  	p.peerIDs = peerIDs
   137  	p.lookup = identities.Lookup()
   138  }
   139  
   140  // Identities returns the full identities of _all_ nodes currently known to the
   141  // protocol that pass the provided filter. Caution, this includes ejected nodes.
   142  // Please check the `Ejected` flag in the identities (or provide a filter for
   143  // removing ejected nodes).
   144  func (p *ProtocolStateIDCache) Identities(filter flow.IdentityFilter[flow.Identity]) flow.IdentityList {
   145  	p.mu.RLock()
   146  	defer p.mu.RUnlock()
   147  	return p.identities.Filter(filter)
   148  }
   149  
   150  // ByNodeID returns the full identity for the node with the given Identifier,
   151  // where Identifier is the way the protocol refers to the node. The function
   152  // has the same semantics as a map lookup, where the boolean return value is
   153  // true if and only if Identity has been found, i.e. `Identity` is not nil.
   154  // Caution: function returns include ejected nodes. Please check the `Ejected`
   155  // flag in the identity.
   156  func (p *ProtocolStateIDCache) ByNodeID(flowID flow.Identifier) (*flow.Identity, bool) {
   157  	p.mu.RLock()
   158  	defer p.mu.RUnlock()
   159  	id, ok := p.lookup[flowID]
   160  	return id, ok
   161  }
   162  
   163  // ByPeerID returns the full identity for the node with the given peer ID,
   164  // where ID is the way the libP2P refers to the node. The function
   165  // has the same semantics as a map lookup, where the boolean return value is
   166  // true if and only if Identity has been found, i.e. `Identity` is not nil.
   167  // Caution: function returns include ejected nodes. Please check the `Ejected`
   168  // flag in the identity.
   169  func (p *ProtocolStateIDCache) ByPeerID(peerID peer.ID) (*flow.Identity, bool) {
   170  	p.mu.RLock()
   171  	defer p.mu.RUnlock()
   172  	if flowID, ok := p.flowIDs[peerID]; ok {
   173  		id, ok := p.lookup[flowID]
   174  		return id, ok
   175  	}
   176  	return nil, false
   177  }
   178  
   179  // GetPeerID returns the peer ID for the given Flow ID.
   180  // During normal operations, the following error returns are expected
   181  //   - ErrUnknownId if the given Identifier is unknown
   182  func (p *ProtocolStateIDCache) GetPeerID(flowID flow.Identifier) (peer.ID, error) {
   183  	p.mu.RLock()
   184  	defer p.mu.RUnlock()
   185  
   186  	pid, found := p.peerIDs[flowID]
   187  	if !found {
   188  		return "", fmt.Errorf("flow ID %v was not found in cached identity list: %w", flowID, p2p.ErrUnknownId)
   189  	}
   190  	return pid, nil
   191  }
   192  
   193  // GetFlowID returns the Flow ID for the given peer ID.
   194  // During normal operations, the following error returns are expected
   195  //   - ErrUnknownId if the given Identifier is unknown
   196  func (p *ProtocolStateIDCache) GetFlowID(peerID peer.ID) (flow.Identifier, error) {
   197  	p.mu.RLock()
   198  	defer p.mu.RUnlock()
   199  
   200  	fid, found := p.flowIDs[peerID]
   201  	if !found {
   202  		return flow.ZeroID, fmt.Errorf("peer ID %v was not found in cached identity list: %w", peerID, p2p.ErrUnknownId)
   203  	}
   204  	return fid, nil
   205  }