github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/channels/channels.go (about) 1 package channels 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/onflow/flow-go/model/flow" 8 ) 9 10 // init is called first time this package is imported. 11 // It creates and initializes channelRoleMap and clusterChannelPrefixRoleMap. 12 func init() { 13 initializeChannelRoleMap() 14 } 15 16 // channelRoleMap keeps a map between channels and the list of flow roles involved in them. 17 var channelRoleMap map[Channel]flow.RoleList 18 19 // clusterChannelPrefixRoleMap keeps a map between cluster channel prefixes and the list of flow roles involved in them. 20 var clusterChannelPrefixRoleMap map[string]flow.RoleList 21 22 // RolesByChannel returns list of flow roles involved in the channel. 23 // If the given channel is a public channel, the returned list will 24 // contain all roles. 25 func RolesByChannel(channel Channel) (flow.RoleList, bool) { 26 if IsClusterChannel(channel) { 27 return ClusterChannelRoles(channel), true 28 } 29 if IsPublicChannel(channel) { 30 return flow.Roles(), true 31 } 32 roles, ok := channelRoleMap[channel] 33 return roles, ok 34 } 35 36 // ChannelExists returns true if the channel exists. 37 func ChannelExists(channel Channel) bool { 38 if _, ok := RolesByChannel(channel); ok { 39 return true 40 } 41 42 return false 43 } 44 45 // ChannelsByRole returns a list of all channels the role subscribes to (except cluster-based channels and public channels). 46 func ChannelsByRole(role flow.Role) ChannelList { 47 channels := make(ChannelList, 0) 48 for channel, roles := range channelRoleMap { 49 if roles.Contains(role) { 50 channels = append(channels, channel) 51 } 52 } 53 54 return channels 55 } 56 57 // UniqueChannels returns list of non-cluster channels with a unique RoleList accompanied 58 // with the list of all cluster channels. 59 // e.g. if channel X and Y both are non-cluster channels and have role IDs [A,B,C] then only one of them will be in the returned list. 60 func UniqueChannels(channels ChannelList) ChannelList { 61 // uniques keeps the set of unique channels based on their RoleList. 62 uniques := make(ChannelList, 0) 63 // added keeps track of channels added to uniques for deduplication. 64 added := make(map[flow.Identifier]struct{}) 65 66 // a channel is added to uniques if it is either a 67 // cluster channel, or no non-cluster channel with the same set of roles 68 // has already been added to uniques. 69 // We use identifier of RoleList to determine its uniqueness. 70 for _, channel := range channels { 71 // non-cluster channel deduplicated based identifier of role list 72 if !IsClusterChannel(channel) { 73 id := channelRoleMap[channel].ID() 74 if _, ok := added[id]; ok { 75 // a channel with same RoleList already added, hence skips 76 continue 77 } 78 added[id] = struct{}{} 79 } 80 81 uniques = append(uniques, channel) 82 } 83 84 return uniques 85 } 86 87 // Channels returns all channels that nodes of any role have subscribed to (except cluster-based channels). 88 func Channels() ChannelList { 89 channels := make(ChannelList, 0) 90 for channel := range channelRoleMap { 91 channels = append(channels, channel) 92 } 93 channels = append(channels, PublicChannels()...) 94 95 return channels 96 } 97 98 // PublicChannels returns all channels that are used on the public network. 99 func PublicChannels() ChannelList { 100 return ChannelList{ 101 PublicSyncCommittee, 102 PublicReceiveBlocks, 103 PublicExecutionDataService, 104 } 105 } 106 107 // IsPublicChannel returns true if channel is in the public channels list 108 func IsPublicChannel(channel Channel) bool { 109 return PublicChannels().Contains(channel) 110 } 111 112 // channels 113 const ( 114 115 // Channels used for testing 116 TestNetworkChannel = Channel("test-network") 117 TestMetricsChannel = Channel("test-metrics") 118 119 // Channels for consensus protocols 120 ConsensusCommittee = Channel("consensus-committee") 121 ConsensusClusterPrefix = "consensus-cluster" // dynamic channel, use ConsensusCluster function 122 123 // Channels for protocols actively synchronizing state across nodes 124 SyncCommittee = Channel("sync-committee") 125 SyncClusterPrefix = "sync-cluster" // dynamic channel, use SyncCluster function 126 127 // Channels for dkg communication 128 DKGCommittee = "dkg-committee" 129 130 // Channels for actively pushing entities to subscribers 131 PushTransactions = Channel("push-transactions") 132 PushGuarantees = Channel("push-guarantees") 133 PushBlocks = Channel("push-blocks") 134 PushReceipts = Channel("push-receipts") 135 PushApprovals = Channel("push-approvals") 136 137 // Channels for actively requesting missing entities 138 RequestCollections = Channel("request-collections") 139 RequestChunks = Channel("request-chunks") 140 RequestReceiptsByBlockID = Channel("request-receipts-by-block-id") 141 RequestApprovalsByChunk = Channel("request-approvals-by-chunk") 142 143 // Channel aliases to make the code more readable / more robust to errors 144 ReceiveTransactions = PushTransactions 145 ReceiveGuarantees = PushGuarantees 146 ReceiveBlocks = PushBlocks 147 ReceiveReceipts = PushReceipts 148 ReceiveApprovals = PushApprovals 149 150 ProvideCollections = RequestCollections 151 ProvideChunks = RequestChunks 152 ProvideReceiptsByBlockID = RequestReceiptsByBlockID 153 ProvideApprovalsByChunk = RequestApprovalsByChunk 154 155 // Public network channels 156 PublicPushBlocks = Channel("public-push-blocks") 157 PublicReceiveBlocks = PublicPushBlocks 158 PublicSyncCommittee = Channel("public-sync-committee") 159 PublicExecutionDataService = Channel("public-execution-data-service") 160 161 // Execution data service 162 ExecutionDataService = Channel("execution-data-service") 163 ) 164 165 // initializeChannelRoleMap initializes an instance of channelRoleMap and populates it 166 // with the channels and their corresponding list of authorized roles. 167 // Note: Please update this map, if a new channel is defined or a the roles subscribing to a channel have changed 168 func initializeChannelRoleMap() { 169 channelRoleMap = make(map[Channel]flow.RoleList) 170 171 // Channels for test 172 channelRoleMap[TestNetworkChannel] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution, 173 flow.RoleVerification, flow.RoleAccess} 174 channelRoleMap[TestMetricsChannel] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution, 175 flow.RoleVerification, flow.RoleAccess} 176 177 // Channels for consensus protocols 178 channelRoleMap[ConsensusCommittee] = flow.RoleList{flow.RoleConsensus} 179 180 // Channels for protocols actively synchronizing state across nodes 181 channelRoleMap[SyncCommittee] = flow.Roles() 182 183 // Channels for DKG communication 184 channelRoleMap[DKGCommittee] = flow.RoleList{flow.RoleConsensus} 185 186 // Channels for actively pushing entities to subscribers 187 channelRoleMap[PushTransactions] = flow.RoleList{flow.RoleCollection} 188 channelRoleMap[PushGuarantees] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus} 189 channelRoleMap[PushBlocks] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution, 190 flow.RoleVerification, flow.RoleAccess} 191 channelRoleMap[PushReceipts] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution, flow.RoleVerification, 192 flow.RoleAccess} 193 channelRoleMap[PushApprovals] = flow.RoleList{flow.RoleConsensus, flow.RoleVerification} 194 195 // Channels for actively requesting missing entities 196 channelRoleMap[RequestCollections] = flow.RoleList{flow.RoleCollection, flow.RoleExecution, flow.RoleAccess} 197 channelRoleMap[RequestChunks] = flow.RoleList{flow.RoleExecution, flow.RoleVerification} 198 channelRoleMap[RequestReceiptsByBlockID] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution} 199 channelRoleMap[RequestApprovalsByChunk] = flow.RoleList{flow.RoleConsensus, flow.RoleVerification} 200 201 // Channel aliases to make the code more readable / more robust to errors 202 channelRoleMap[ReceiveGuarantees] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus} 203 channelRoleMap[ReceiveBlocks] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution, 204 flow.RoleVerification, flow.RoleAccess} 205 channelRoleMap[ReceiveReceipts] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution, flow.RoleVerification, 206 flow.RoleAccess} 207 channelRoleMap[ReceiveApprovals] = flow.RoleList{flow.RoleConsensus, flow.RoleVerification} 208 209 channelRoleMap[ProvideCollections] = flow.RoleList{flow.RoleCollection, flow.RoleExecution, flow.RoleAccess} 210 channelRoleMap[ProvideChunks] = flow.RoleList{flow.RoleExecution, flow.RoleVerification} 211 channelRoleMap[ProvideReceiptsByBlockID] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution} 212 channelRoleMap[ProvideApprovalsByChunk] = flow.RoleList{flow.RoleConsensus, flow.RoleVerification} 213 214 clusterChannelPrefixRoleMap = make(map[string]flow.RoleList) 215 216 clusterChannelPrefixRoleMap[SyncClusterPrefix] = flow.RoleList{flow.RoleCollection} 217 clusterChannelPrefixRoleMap[ConsensusClusterPrefix] = flow.RoleList{flow.RoleCollection} 218 } 219 220 // ClusterChannelRoles returns the list of roles that are involved in the given cluster-based channel. 221 func ClusterChannelRoles(clusterChannel Channel) flow.RoleList { 222 if prefix, ok := ClusterChannelPrefix(clusterChannel); ok { 223 return clusterChannelPrefixRoleMap[prefix] 224 } 225 226 return flow.RoleList{} 227 } 228 229 // ClusterChannelPrefix returns the cluster channel prefix and true if clusterChannel exists inclusterChannelPrefixRoleMap 230 func ClusterChannelPrefix(clusterChannel Channel) (string, bool) { 231 for prefix := range clusterChannelPrefixRoleMap { 232 if strings.HasPrefix(clusterChannel.String(), prefix) { 233 return prefix, true 234 } 235 } 236 237 return "", false 238 } 239 240 // IsClusterChannel returns true if channel is cluster-based. 241 // Currently, only collection nodes are involved in a cluster-based channels. 242 func IsClusterChannel(channel Channel) bool { 243 _, ok := ClusterChannelPrefix(channel) 244 return ok 245 } 246 247 // TopicFromChannel returns the unique LibP2P topic form the channel. 248 // The channel is made up of name string suffixed with root block id. 249 // The root block id is used to prevent cross talks between nodes on different sporks. 250 func TopicFromChannel(channel Channel, rootBlockID flow.Identifier) Topic { 251 // skip root block suffix, if this is a cluster specific channel. A cluster specific channel is inherently 252 // unique for each epoch 253 if IsClusterChannel(channel) { 254 return Topic(channel) 255 } 256 return Topic(fmt.Sprintf("%s/%s", string(channel), rootBlockID.String())) 257 } 258 259 // TopicsFromChannels returns the unique LibP2P topics form the channels. 260 func TopicsFromChannels(channels ChannelList, rootBlockID flow.Identifier) []Topic { 261 topics := make([]Topic, 0, len(channels)) 262 for _, channel := range channels { 263 topics = append(topics, TopicFromChannel(channel, rootBlockID)) 264 } 265 return topics 266 } 267 268 func ChannelFromTopic(topic Topic) (Channel, bool) { 269 if IsClusterChannel(Channel(topic)) { 270 return Channel(topic), true 271 } 272 273 if index := strings.LastIndex(topic.String(), "/"); index != -1 { 274 return Channel(topic[:index]), true 275 } 276 277 return "", false 278 } 279 280 // sporkIdFromTopic returns the pre-pended spork ID flow identifier for the topic. 281 // A valid channel has a spork ID suffix: 282 // 283 // channel/spork_id 284 // 285 // A generic error is returned if an error is encountered while converting the spork ID to flow Identifier or 286 // the spork ID is missing. 287 func sporkIdFromTopic(topic Topic) (flow.Identifier, error) { 288 if index := strings.LastIndex(topic.String(), "/"); index != -1 { 289 id, err := flow.HexStringToIdentifier(string(topic)[index+1:]) 290 if err != nil { 291 return flow.Identifier{}, fmt.Errorf("failed to get spork ID from topic %s: %w", topic, err) 292 } 293 294 return id, nil 295 } 296 return flow.Identifier{}, fmt.Errorf("spork id missing from topic") 297 } 298 299 // sporkIdStrFromTopic returns the pre-pended spork ID string for the topic. 300 // A valid channel has a spork ID suffix: 301 // 302 // channel/spork_id 303 // 304 // A generic error is returned if an error is encountered while deriving the spork ID from the topic 305 func sporkIdStrFromTopic(topic Topic) (string, error) { 306 sporkId, err := sporkIdFromTopic(topic) 307 if err != nil { 308 return "", err 309 } 310 return sporkId.String(), nil 311 } 312 313 // clusterIDStrFromTopic returns the appended cluster ID in flow.ChainID format for the cluster prefixed topic. 314 // A valid cluster-prefixed channel includes the cluster prefix and cluster ID suffix: 315 // 316 // sync-cluster/some_cluster_id 317 // 318 // A generic error is returned if the topic is malformed. 319 func clusterIDStrFromTopic(topic Topic) (flow.ChainID, error) { 320 for prefix := range clusterChannelPrefixRoleMap { 321 if strings.HasPrefix(topic.String(), prefix) { 322 return flow.ChainID(strings.TrimPrefix(topic.String(), fmt.Sprintf("%s-", prefix))), nil 323 } 324 } 325 return "", fmt.Errorf("failed to get cluster ID from topic %s", topic) 326 } 327 328 // ConsensusCluster returns a dynamic cluster consensus channel based on 329 // the chain ID of the cluster in question. 330 func ConsensusCluster(clusterID flow.ChainID) Channel { 331 return Channel(fmt.Sprintf("%s-%s", ConsensusClusterPrefix, clusterID)) 332 } 333 334 // SyncCluster returns a dynamic cluster sync channel based on the chain 335 // ID of the cluster in question. 336 func SyncCluster(clusterID flow.ChainID) Channel { 337 return Channel(fmt.Sprintf("%s-%s", SyncClusterPrefix, clusterID)) 338 } 339 340 // IsValidNonClusterFlowTopic ensures the topic is a valid Flow network topic and 341 // ensures the sporkID part of the Topic is equal to the current network sporkID. 342 // Expected errors: 343 // - InvalidTopicErr if the topic is not a if the topic is not a valid topic for the given spork. 344 func IsValidNonClusterFlowTopic(topic Topic, expectedSporkID flow.Identifier) error { 345 sporkID, err := sporkIdStrFromTopic(topic) 346 if err != nil { 347 return NewInvalidTopicErr(topic, fmt.Errorf("failed to get spork ID from topic: %w", err)) 348 } 349 350 if sporkID != expectedSporkID.String() { 351 return NewInvalidTopicErr(topic, fmt.Errorf("invalid flow topic mismatch spork ID expected spork ID %s actual spork ID %s", expectedSporkID, sporkID)) 352 } 353 354 return isValidFlowTopic(topic) 355 } 356 357 // IsValidFlowClusterTopic ensures the topic is a valid Flow network topic and 358 // ensures the cluster ID part of the Topic is equal to one of the provided active cluster IDs. 359 // All errors returned from this function can be considered benign. 360 // Expected errors: 361 // - InvalidTopicErr if the topic is not a valid Flow topic or the cluster ID cannot be derived from the topic. 362 // - UnknownClusterIDErr if the cluster ID from the topic is not in the activeClusterIDS list. 363 func IsValidFlowClusterTopic(topic Topic, activeClusterIDS flow.ChainIDList) error { 364 err := isValidFlowTopic(topic) 365 if err != nil { 366 return err 367 } 368 369 clusterID, err := clusterIDStrFromTopic(topic) 370 if err != nil { 371 return NewInvalidTopicErr(topic, fmt.Errorf("failed to get cluster ID from topic: %w", err)) 372 } 373 374 for _, activeClusterID := range activeClusterIDS { 375 if clusterID == activeClusterID { 376 return nil 377 } 378 } 379 380 return NewUnknownClusterIdErr(clusterID, activeClusterIDS) 381 } 382 383 // isValidFlowTopic ensures the topic is a valid Flow network topic. 384 // A valid Topic has the following properties: 385 // - A Channel can be derived from the Topic and that channel exists. 386 // Expected errors: 387 // - InvalidTopicErr if the topic is not a valid Flow topic. 388 func isValidFlowTopic(topic Topic) error { 389 channel, ok := ChannelFromTopic(topic) 390 if !ok { 391 return NewInvalidTopicErr(topic, fmt.Errorf("invalid topic: failed to get channel from topic")) 392 } 393 err := IsValidFlowChannel(channel) 394 if err != nil { 395 return NewInvalidTopicErr(topic, fmt.Errorf("invalid topic: %w", err)) 396 } 397 return nil 398 } 399 400 // IsValidFlowChannel ensures the channel is a valid Flow network channel. 401 // All errors returned from this function can be considered benign. 402 func IsValidFlowChannel(channel Channel) error { 403 if !ChannelExists(channel) { 404 return fmt.Errorf("unknown channel: %s", channel) 405 } 406 return nil 407 }