github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/pkg/gateway/registry.go (about) 1 /* 2 Copyright 2021 IBM All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package gateway 8 9 import ( 10 "bytes" 11 "fmt" 12 "math/rand" 13 "sort" 14 "strings" 15 "sync" 16 17 "github.com/golang/protobuf/proto" 18 "github.com/hechain20/hechain/common/channelconfig" 19 "github.com/hechain20/hechain/common/flogging" 20 gossipapi "github.com/hechain20/hechain/gossip/api" 21 gossipcommon "github.com/hechain20/hechain/gossip/common" 22 gossipdiscovery "github.com/hechain20/hechain/gossip/discovery" 23 dp "github.com/hyperledger/fabric-protos-go/discovery" 24 "github.com/hyperledger/fabric-protos-go/gossip" 25 "github.com/hyperledger/fabric-protos-go/peer" 26 ) 27 28 type Discovery interface { 29 Config(channel string) (*dp.ConfigResult, error) 30 IdentityInfo() gossipapi.PeerIdentitySet 31 PeersForEndorsement(channel gossipcommon.ChannelID, interest *peer.ChaincodeInterest) (*dp.EndorsementDescriptor, error) 32 PeersOfChannel(gossipcommon.ChannelID) gossipdiscovery.Members 33 } 34 35 type registry struct { 36 localEndorser *endorser 37 discovery Discovery 38 logger *flogging.FabricLogger 39 endpointFactory *endpointFactory 40 remoteEndorsers map[string]*endorser 41 broadcastClients sync.Map // orderer address (string) -> client connection (orderer) 42 channelInitialized map[string]bool 43 configLock sync.RWMutex 44 channelOrderers sync.Map // channel (string) -> orderer addresses (endpointConfig) 45 } 46 47 type endorserState struct { 48 peer *dp.Peer 49 endorser *endorser 50 height uint64 51 } 52 53 // Returns an endorsementPlan for the given chaincode on a channel. 54 func (reg *registry) endorsementPlan(channel string, interest *peer.ChaincodeInterest, preferredEndorser *endorser) (*plan, error) { 55 descriptor, err := reg.discovery.PeersForEndorsement(gossipcommon.ChannelID(channel), interest) 56 if err != nil { 57 logger.Errorw("PeersForEndorsement failed.", "error", err, "channel", channel, "ChaincodeInterest", proto.MarshalTextString(interest)) 58 return nil, fmt.Errorf("no combination of peers can be derived which satisfy the endorsement policy: %s", err) 59 } 60 61 // There are two parts to an endorsement plan: 62 // 1) List of endorsers in each group 63 // 2) Layouts consisting of a number of endorsers from each group 64 65 // Firstly, process the endorsers by group 66 // Create a map of groupIds to list of endorsers, sorted by decreasing block height 67 // Also build a map of endorser pkiid to groupId 68 groupEndorsers := map[string][]*endorser{} 69 var preferredGroup string 70 var unavailableEndorsers []string 71 72 for group, peers := range descriptor.GetEndorsersByGroups() { 73 var groupPeers []*endorserState 74 for _, peer := range peers.GetPeers() { 75 // extract block height 76 msg := &gossip.GossipMessage{} 77 err = proto.Unmarshal(peer.GetStateInfo().GetPayload(), msg) 78 if err != nil { 79 return nil, err 80 } 81 height := msg.GetStateInfo().GetProperties().GetLedgerHeight() 82 83 // extract endpoint 84 err = proto.Unmarshal(peer.GetMembershipInfo().GetPayload(), msg) 85 if err != nil { 86 return nil, err 87 } 88 member := msg.GetAliveMsg().GetMembership() 89 90 // find the endorser in the registry for this endpoint 91 endorser := reg.lookupEndorser(member.GetEndpoint(), member.GetPkiId(), channel) 92 if endorser == nil { 93 unavailableEndorsers = append(unavailableEndorsers, member.GetEndpoint()) 94 continue 95 } 96 if endorser == preferredEndorser { 97 preferredGroup = group 98 } 99 groupPeers = append(groupPeers, &endorserState{peer: peer, endorser: endorser, height: height}) 100 } 101 // sort by decreasing height 102 sort.Slice(groupPeers, sorter(groupPeers, reg.localEndorser.address)) 103 104 if len(groupPeers) > 0 { 105 var endorsers []*endorser 106 for _, peer := range groupPeers { 107 endorsers = append(endorsers, peer.endorser) 108 } 109 groupEndorsers[group] = endorsers 110 } 111 } 112 113 // Second, process the layouts 114 // Group them by groupId and arrange them so that layouts containing the 'preferredOrg' are at the front of the list 115 var preferredLayouts []*layout 116 var otherLayouts []*layout 117 layout: 118 for _, lo := range descriptor.GetLayouts() { 119 hasPreferredGroup := false 120 quantityByGroup := map[string]int{} 121 for group, quantity := range lo.GetQuantitiesByGroup() { 122 // if there's no entry in this map, meaning there aren't any available endorsers for this group 123 if len(groupEndorsers[group]) < int(quantity) { 124 // The number of available endorsers less than the quantity required, so abandon this layout 125 continue layout 126 } 127 quantityByGroup[group] = int(quantity) 128 if group == preferredGroup { 129 hasPreferredGroup = true 130 } 131 } 132 if len(quantityByGroup) == 0 { 133 // empty layout 134 break 135 } 136 if hasPreferredGroup { 137 preferredLayouts = append(preferredLayouts, &layout{required: quantityByGroup}) 138 } else { 139 otherLayouts = append(otherLayouts, &layout{required: quantityByGroup}) 140 } 141 } 142 143 // shuffle the layouts - keep the preferred ones first 144 rand.Shuffle(len(preferredLayouts), func(i, j int) { preferredLayouts[i], preferredLayouts[j] = preferredLayouts[j], preferredLayouts[i] }) 145 rand.Shuffle(len(otherLayouts), func(i, j int) { otherLayouts[i], otherLayouts[j] = otherLayouts[j], otherLayouts[i] }) 146 layouts := append(preferredLayouts, otherLayouts...) 147 148 if len(layouts) == 0 { 149 return nil, fmt.Errorf("failed to select a set of endorsers that satisfy the endorsement policy due to unavailability of peers: %v", unavailableEndorsers) 150 } 151 152 return newPlan(layouts, groupEndorsers), nil 153 } 154 155 // planForOrgs generates an endorsement plan with a single layout requiring one peer from each of the given orgs for the given chaincode on a channel. 156 func (reg *registry) planForOrgs(channel string, chaincode string, endorsingOrgs []string) (*plan, error) { 157 endorsersByOrg := reg.endorsersByOrg(channel, chaincode) 158 159 required := map[string]int{} 160 groupEndorsers := map[string][]*endorser{} 161 missingOrgs := []string{} 162 for _, org := range endorsingOrgs { 163 if es, ok := endorsersByOrg[org]; ok { 164 for _, e := range es { 165 groupEndorsers[org] = append(groupEndorsers[org], e.endorser) 166 } 167 required[org] = 1 168 } else { 169 missingOrgs = append(missingOrgs, org) 170 } 171 } 172 if len(missingOrgs) > 0 { 173 return nil, fmt.Errorf("failed to find any endorsing peers for org(s): %s", strings.Join(missingOrgs, ", ")) 174 } 175 176 return newPlan([]*layout{{required: required}}, groupEndorsers), nil 177 } 178 179 func (reg *registry) endorsersByOrg(channel string, chaincode string) map[string][]*endorserState { 180 endorsersByOrg := make(map[string][]*endorserState) 181 182 members := reg.discovery.PeersOfChannel(gossipcommon.ChannelID(channel)) 183 184 for _, member := range members { 185 endorser := reg.lookupEndorser(member.PreferredEndpoint(), member.PKIid, channel) 186 if endorser == nil { 187 continue 188 } 189 for _, installedChaincode := range member.Properties.GetChaincodes() { 190 // only consider the peers that have our chaincode installed 191 if installedChaincode.GetName() == chaincode { 192 endorsersByOrg[endorser.mspid] = append(endorsersByOrg[endorser.mspid], &endorserState{endorser: endorser, height: member.Properties.GetLedgerHeight()}) 193 } 194 } 195 for _, es := range endorsersByOrg { 196 // sort by decreasing height in each org 197 sort.Slice(es, sorter(es, reg.localEndorser.address)) 198 } 199 } 200 return endorsersByOrg 201 } 202 203 // evaluator returns a plan representing a single endorsement, preferably from local org, if available 204 // targetOrgs specifies the orgs that are allowed receive the request, due to private data restrictions 205 func (reg *registry) evaluator(channel string, chaincode string, targetOrgs []string) (*plan, error) { 206 endorsersByOrg := reg.endorsersByOrg(channel, chaincode) 207 208 // If no targetOrgs are specified (i.e. no restrictions), then populate with all available orgs 209 if len(targetOrgs) == 0 { 210 for org := range endorsersByOrg { 211 targetOrgs = append(targetOrgs, org) 212 } 213 } 214 215 localOrgEndorsers := []*endorserState{} 216 otherOrgEndorsers := []*endorserState{} 217 for _, org := range targetOrgs { 218 if es, ok := endorsersByOrg[org]; ok { 219 if org == reg.localEndorser.mspid { 220 localOrgEndorsers = es 221 } else { 222 otherOrgEndorsers = append(otherOrgEndorsers, es...) 223 } 224 } 225 } 226 // sort all the 'other orgs' endorsers by decreasing block height 227 sort.Slice(otherOrgEndorsers, sorter(otherOrgEndorsers, "")) 228 229 var allEndorsers []*endorser 230 for _, e := range append(localOrgEndorsers, otherOrgEndorsers...) { 231 allEndorsers = append(allEndorsers, e.endorser) 232 } 233 if len(allEndorsers) > 0 { 234 layout := []*layout{{required: map[string]int{"g1": 1}}} // single layout, one group, one endorsement 235 groupEndorsers := map[string][]*endorser{"g1": allEndorsers} 236 return newPlan(layout, groupEndorsers), nil 237 } 238 return nil, fmt.Errorf("no peers available to evaluate chaincode %s in channel %s", chaincode, channel) 239 } 240 241 func sorter(e []*endorserState, host string) func(i, j int) bool { 242 return func(i, j int) bool { 243 if e[i].height == e[j].height { 244 // prefer host peer 245 return e[i].endorser.address == host 246 } 247 return e[i].height > e[j].height 248 } 249 } 250 251 // Returns a set of broadcastClients that can order a transaction for the given channel. 252 func (reg *registry) orderers(channel string) ([]*orderer, error) { 253 var orderers []*orderer 254 var ordererEndpoints []*endpointConfig 255 addr, exists := reg.channelOrderers.Load(channel) 256 // if it doesn't exist, get the orderers config for this channel 257 if exists { 258 ordererEndpoints = addr.([]*endpointConfig) 259 } else { 260 // no entry in the map - get the orderer config from discovery 261 channelOrderers, err := reg.config(channel) 262 if err != nil { 263 return nil, err 264 } 265 // A config update may have saved this first, in which case don't overwrite it. 266 addr, _ = reg.channelOrderers.LoadOrStore(channel, channelOrderers) 267 ordererEndpoints = addr.([]*endpointConfig) 268 } 269 for _, ep := range ordererEndpoints { 270 entry, exists := reg.broadcastClients.Load(ep.address) 271 if !exists { 272 // this orderer is new - connect to it and add to the broadcastClients registry 273 client, err := reg.endpointFactory.newOrderer(ep.address, ep.mspid, ep.tlsRootCerts) 274 if err != nil { 275 // Failed to connect to this orderer for some reason. Log the problem and skip to the next one. 276 reg.logger.Warnw("Failed to connect to orderer", "address", ep.address, "err", err) 277 continue 278 } 279 var loaded bool 280 entry, loaded = reg.broadcastClients.LoadOrStore(ep.address, client) 281 if loaded { 282 // another goroutine got there first, close this new connection 283 err = client.closeConnection() 284 if err != nil { 285 // Failed to close this new connection. Log the problem. 286 reg.logger.Warnw("Failed to close connection to orderer", "address", ep.address, "err", err) 287 } 288 } else { 289 reg.logger.Infow("Added orderer to registry", "address", ep.address) 290 } 291 } 292 orderers = append(orderers, entry.(*orderer)) 293 } 294 295 return orderers, nil 296 } 297 298 func (reg *registry) lookupEndorser(endpoint string, pkiid gossipcommon.PKIidType, channel string) *endorser { 299 lookup := func() (*endorser, bool) { 300 reg.configLock.RLock() 301 defer reg.configLock.RUnlock() 302 303 // find the endorser in the registry for this endpoint 304 if bytes.Equal(pkiid, reg.localEndorser.pkiid) { 305 logger.Debugw("Found local endorser", "pkiid", pkiid) 306 return reg.localEndorser, false 307 } 308 if endpoint == "" { 309 reg.logger.Warnw("No endpoint for endorser with PKI ID %s", "pkiid", pkiid.String()) 310 return nil, false 311 } 312 if e, ok := reg.remoteEndorsers[endpoint]; ok { 313 logger.Debugw("Found remote endorser", "endpoint", endpoint) 314 return e, false 315 } 316 // not found - try to connect 317 return nil, true 318 } 319 endorser, connect := lookup() 320 if connect { 321 reg.logger.Infow("Attempting to connect to endorser", "endpoint", endpoint) 322 // for efficiency, try to connect to all peers in the channel in one pass 323 err := reg.connectChannelPeers(channel, true) 324 if err != nil { 325 logger.Errorw("Failed to reconnect", "endpoint", endpoint, "err", err) 326 } 327 endorser, _ = lookup() // if it failed to reconnect, this will be nil 328 } 329 return endorser 330 } 331 332 // Connect to peers in channel, and add to the registry. If a reconnection is required, then it must be removed 333 // from the registry first, using removeEndorser(). 334 func (reg *registry) connectChannelPeers(channel string, force bool) error { 335 reg.configLock.Lock() // take a write lock to populate the registry maps 336 defer reg.configLock.Unlock() 337 338 if !force && reg.channelInitialized[channel] { 339 return nil 340 } 341 342 // get the remoteEndorsers for the channel 343 peers := map[string]string{} 344 members := reg.discovery.PeersOfChannel(gossipcommon.ChannelID(channel)) 345 for _, member := range members { 346 id := member.PKIid.String() 347 peers[id] = member.PreferredEndpoint() 348 } 349 config, err := reg.discovery.Config(channel) 350 if err != nil { 351 return fmt.Errorf("failed to get config for channel [%s]: %w", channel, err) 352 } 353 for mspid, infoset := range reg.discovery.IdentityInfo().ByOrg() { 354 var tlsRootCerts [][]byte 355 if mspInfo, ok := config.GetMsps()[mspid]; ok { 356 tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsRootCerts()...) 357 tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsIntermediateCerts()...) 358 } 359 for _, info := range infoset { 360 pkiid := info.PKIId 361 if address, ok := peers[pkiid.String()]; ok { 362 // add the peer to the peer map - except the local peer, which seems to have an empty address 363 if _, ok := reg.remoteEndorsers[address]; !ok && len(address) > 0 { 364 // this peer is new - connect to it and add to the remoteEndorsers registry 365 endorser, err := reg.endpointFactory.newEndorser(pkiid, address, mspid, tlsRootCerts) 366 if err != nil { 367 return err 368 } 369 reg.remoteEndorsers[address] = endorser 370 reg.logger.Infof("Added peer to registry: %s", address) 371 } 372 } 373 } 374 } 375 reg.channelInitialized[channel] = true 376 return nil 377 } 378 379 // removeEndorser closes the connection and removes from the registry, but if the next call to discovery returns that 380 // peer in its plan, then it will attempt to reconnect and add it back. This could happen if the peer had gone down, 381 // but the gossip has not notified the discovery service yet. 382 func (reg *registry) removeEndorser(endorser *endorser) { 383 if endorser == reg.localEndorser { 384 // nothing to close 385 return 386 } 387 reg.configLock.Lock() 388 defer reg.configLock.Unlock() 389 390 err := endorser.closeConnection() 391 if err != nil { 392 reg.logger.Errorw("Failed to close connection to endorser", "address", endorser.address, "mspid", endorser.mspid, "err", err) 393 } 394 delete(reg.remoteEndorsers, endorser.address) 395 } 396 397 func (reg *registry) config(channel string) ([]*endpointConfig, error) { 398 config, err := reg.discovery.Config(channel) 399 if err != nil { 400 return nil, fmt.Errorf("failed to get config for channel [%s]: %w", channel, err) 401 } 402 var channelOrderers []*endpointConfig 403 for mspid, eps := range config.GetOrderers() { 404 var tlsRootCerts [][]byte 405 if mspInfo, ok := config.GetMsps()[mspid]; ok { 406 tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsRootCerts()...) 407 tlsRootCerts = append(tlsRootCerts, mspInfo.GetTlsIntermediateCerts()...) 408 } 409 for _, ep := range eps.Endpoint { 410 address := fmt.Sprintf("%s:%d", ep.Host, ep.Port) 411 channelOrderers = append(channelOrderers, &endpointConfig{address: address, mspid: mspid, tlsRootCerts: tlsRootCerts}) 412 } 413 } 414 return channelOrderers, nil 415 } 416 417 func (reg *registry) configUpdate(bundle *channelconfig.Bundle) { 418 if ordererConfig, ok := bundle.OrdererConfig(); ok { 419 // orderer config has changed - process the bundle 420 channel := bundle.ConfigtxValidator().ChannelID() 421 reg.logger.Infow("Updating orderer config", "channel", channel) 422 var channelOrderers []*endpointConfig 423 for _, org := range ordererConfig.Organizations() { 424 mspid := org.MSPID() 425 msp := org.MSP() 426 tlsRootCerts := append([][]byte{}, msp.GetTLSRootCerts()...) 427 tlsRootCerts = append(tlsRootCerts, msp.GetTLSIntermediateCerts()...) 428 for _, address := range org.Endpoints() { 429 channelOrderers = append(channelOrderers, &endpointConfig{address: address, mspid: mspid, tlsRootCerts: tlsRootCerts}) 430 reg.logger.Debugw("Channel orderer", "address", address, "mspid", mspid) 431 } 432 } 433 if len(channelOrderers) > 0 { 434 reg.closeStaleOrdererConnections(channel, channelOrderers) 435 reg.channelOrderers.Store(channel, channelOrderers) 436 } 437 } 438 } 439 440 func (reg *registry) closeStaleOrdererConnections(channel string, channelOrderers []*endpointConfig) { 441 // Load the list of orderers that is about to be overwritten, if loaded is false, then another goroutine got there first 442 oldList, loaded := reg.channelOrderers.LoadAndDelete(channel) 443 if loaded { 444 currentEndpoints := map[string]struct{}{} 445 reg.channelOrderers.Range(func(key, value interface{}) bool { 446 for _, ep := range value.([]*endpointConfig) { 447 currentEndpoints[ep.address] = struct{}{} 448 } 449 return true 450 }) 451 for _, ep := range channelOrderers { 452 currentEndpoints[ep.address] = struct{}{} 453 } 454 // if there are any in the oldEndpoints that are not in the currentEndpoints, then remove from registry and close connection 455 for _, ep := range oldList.([]*endpointConfig) { 456 if _, exists := currentEndpoints[ep.address]; !exists { 457 client, found := reg.broadcastClients.LoadAndDelete(ep.address) 458 if found { 459 err := client.(*orderer).closeConnection() 460 if err != nil { 461 reg.logger.Errorw("Failed to close connection to orderer", "address", ep.address, "mspid", ep.mspid, "err", err) 462 } 463 } 464 } 465 } 466 } 467 }