github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/model/flow" 11 "github.com/koko1123/flow-go-1/model/flow/filter" 12 "github.com/koko1123/flow-go-1/module" 13 "github.com/koko1123/flow-go-1/network/p2p" 14 "github.com/koko1123/flow-go-1/network/p2p/keyutils" 15 "github.com/koko1123/flow-go-1/state/protocol" 16 "github.com/koko1123/flow-go-1/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.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 }