github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+incompatible/gossip/privdata/distributor.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package privdata 8 9 import ( 10 "bytes" 11 "fmt" 12 "math/rand" 13 "sync" 14 "sync/atomic" 15 "time" 16 17 "github.com/golang/protobuf/proto" 18 protosgossip "github.com/hyperledger/fabric-protos-go/gossip" 19 "github.com/hyperledger/fabric-protos-go/ledger/rwset" 20 "github.com/hyperledger/fabric-protos-go/peer" 21 "github.com/hyperledger/fabric-protos-go/transientstore" 22 "github.com/hyperledger/fabric/core/common/privdata" 23 "github.com/hyperledger/fabric/gossip/api" 24 gossipCommon "github.com/hyperledger/fabric/gossip/common" 25 "github.com/hyperledger/fabric/gossip/discovery" 26 "github.com/hyperledger/fabric/gossip/filter" 27 gossipgossip "github.com/hyperledger/fabric/gossip/gossip" 28 "github.com/hyperledger/fabric/gossip/metrics" 29 "github.com/hyperledger/fabric/gossip/protoext" 30 "github.com/hyperledger/fabric/gossip/util" 31 "github.com/hyperledger/fabric/msp" 32 "github.com/hyperledger/fabric/protoutil" 33 "github.com/pkg/errors" 34 ) 35 36 // gossipAdapter an adapter for API's required from gossip module 37 type gossipAdapter interface { 38 // SendByCriteria sends a given message to all peers that match the given SendCriteria 39 SendByCriteria(message *protoext.SignedGossipMessage, criteria gossipgossip.SendCriteria) error 40 41 // PeerFilter receives a SubChannelSelectionCriteria and returns a RoutingFilter that selects 42 // only peer identities that match the given criteria, and that they published their channel participation 43 PeerFilter(channel gossipCommon.ChannelID, messagePredicate api.SubChannelSelectionCriteria) (filter.RoutingFilter, error) 44 45 // IdentityInfo returns information known peer identities 46 IdentityInfo() api.PeerIdentitySet 47 48 // PeersOfChannel returns the NetworkMembers considered alive 49 // and also subscribed to the channel given 50 PeersOfChannel(gossipCommon.ChannelID) []discovery.NetworkMember 51 } 52 53 // PvtDataDistributor interface to defines API of distributing private data 54 type PvtDataDistributor interface { 55 // Distribute broadcast reliably private data read write set based on policies 56 Distribute(txID string, privData *transientstore.TxPvtReadWriteSetWithConfigInfo, blkHt uint64) error 57 } 58 59 // IdentityDeserializerFactory is a factory interface to create 60 // IdentityDeserializer for given channel 61 type IdentityDeserializerFactory interface { 62 // GetIdentityDeserializer returns an IdentityDeserializer 63 // instance for the specified chain 64 GetIdentityDeserializer(chainID string) msp.IdentityDeserializer 65 } 66 67 // IdentityDeserializerFactoryFunc is a function adapter for 68 // IdentityDeserializerFactory. 69 type IdentityDeserializerFactoryFunc func(chainID string) msp.IdentityDeserializer 70 71 func (i IdentityDeserializerFactoryFunc) GetIdentityDeserializer(chainID string) msp.IdentityDeserializer { 72 return i(chainID) 73 } 74 75 // distributorImpl the implementation of the private data distributor interface 76 type distributorImpl struct { 77 chainID string 78 gossipAdapter 79 CollectionAccessFactory 80 pushAckTimeout time.Duration 81 metrics *metrics.PrivdataMetrics 82 } 83 84 // CollectionAccessFactory an interface to generate collection access policy 85 type CollectionAccessFactory interface { 86 // AccessPolicy based on collection configuration 87 AccessPolicy(config *peer.CollectionConfig, chainID string) (privdata.CollectionAccessPolicy, error) 88 } 89 90 // policyAccessFactory the implementation of CollectionAccessFactory 91 type policyAccessFactory struct { 92 IdentityDeserializerFactory 93 } 94 95 func (p *policyAccessFactory) AccessPolicy(config *peer.CollectionConfig, chainID string) (privdata.CollectionAccessPolicy, error) { 96 colAP := &privdata.SimpleCollection{} 97 switch cconf := config.Payload.(type) { 98 case *peer.CollectionConfig_StaticCollectionConfig: 99 err := colAP.Setup(cconf.StaticCollectionConfig, p.GetIdentityDeserializer(chainID)) 100 if err != nil { 101 return nil, errors.WithMessagef(err, "error setting up collection %#v", cconf.StaticCollectionConfig.Name) 102 } 103 default: 104 return nil, errors.New("unexpected collection type") 105 } 106 return colAP, nil 107 } 108 109 // NewCollectionAccessFactory 110 func NewCollectionAccessFactory(factory IdentityDeserializerFactory) CollectionAccessFactory { 111 return &policyAccessFactory{ 112 IdentityDeserializerFactory: factory, 113 } 114 } 115 116 // NewDistributor a constructor for private data distributor capable to send 117 // private read write sets for underlying collection 118 func NewDistributor(chainID string, gossip gossipAdapter, factory CollectionAccessFactory, 119 metrics *metrics.PrivdataMetrics, pushAckTimeout time.Duration) PvtDataDistributor { 120 return &distributorImpl{ 121 chainID: chainID, 122 gossipAdapter: gossip, 123 CollectionAccessFactory: factory, 124 pushAckTimeout: pushAckTimeout, 125 metrics: metrics, 126 } 127 } 128 129 // Distribute broadcast reliably private data read write set based on policies 130 func (d *distributorImpl) Distribute(txID string, privData *transientstore.TxPvtReadWriteSetWithConfigInfo, blkHt uint64) error { 131 disseminationPlan, err := d.computeDisseminationPlan(txID, privData, blkHt) 132 if err != nil { 133 return errors.WithStack(err) 134 } 135 return d.disseminate(disseminationPlan) 136 } 137 138 type dissemination struct { 139 msg *protoext.SignedGossipMessage 140 criteria gossipgossip.SendCriteria 141 } 142 143 func (d *distributorImpl) computeDisseminationPlan(txID string, 144 privDataWithConfig *transientstore.TxPvtReadWriteSetWithConfigInfo, 145 blkHt uint64) ([]*dissemination, error) { 146 privData := privDataWithConfig.PvtRwset 147 var disseminationPlan []*dissemination 148 for _, pvtRwset := range privData.NsPvtRwset { 149 namespace := pvtRwset.Namespace 150 configPackage, found := privDataWithConfig.CollectionConfigs[namespace] 151 if !found { 152 logger.Error("Collection config package for", namespace, "chaincode is not provided") 153 return nil, errors.New(fmt.Sprint("collection config package for", namespace, "chaincode is not provided")) 154 } 155 156 for _, collection := range pvtRwset.CollectionPvtRwset { 157 colCP, err := d.getCollectionConfig(configPackage, collection) 158 collectionName := collection.CollectionName 159 if err != nil { 160 logger.Error("Could not find collection access policy for", namespace, " and collection", collectionName, "error", err) 161 return nil, errors.WithMessage(err, fmt.Sprint("could not find collection access policy for", namespace, " and collection", collectionName, "error", err)) 162 } 163 164 colAP, err := d.AccessPolicy(colCP, d.chainID) 165 if err != nil { 166 logger.Error("Could not obtain collection access policy, collection name", collectionName, "due to", err) 167 return nil, errors.Wrap(err, fmt.Sprint("Could not obtain collection access policy, collection name", collectionName, "due to", err)) 168 } 169 170 colFilter := colAP.AccessFilter() 171 if colFilter == nil { 172 logger.Error("Collection access policy for", collectionName, "has no filter") 173 return nil, errors.Errorf("No collection access policy filter computed for %v", collectionName) 174 } 175 176 pvtDataMsg, err := d.createPrivateDataMessage(txID, namespace, collection, &peer.CollectionConfigPackage{Config: []*peer.CollectionConfig{colCP}}, blkHt) 177 if err != nil { 178 return nil, errors.WithStack(err) 179 } 180 181 logger.Debugf("Computing dissemination plan for collection [%s]", collectionName) 182 dPlan, err := d.disseminationPlanForMsg(colAP, colFilter, pvtDataMsg) 183 if err != nil { 184 return nil, errors.WithStack(err) 185 } 186 disseminationPlan = append(disseminationPlan, dPlan...) 187 } 188 } 189 return disseminationPlan, nil 190 } 191 192 func (d *distributorImpl) getCollectionConfig(config *peer.CollectionConfigPackage, collection *rwset.CollectionPvtReadWriteSet) (*peer.CollectionConfig, error) { 193 for _, c := range config.Config { 194 if staticConfig := c.GetStaticCollectionConfig(); staticConfig != nil { 195 if staticConfig.Name == collection.CollectionName { 196 return c, nil 197 } 198 } 199 } 200 return nil, errors.New(fmt.Sprint("no configuration for collection", collection.CollectionName, "found")) 201 } 202 203 func (d *distributorImpl) disseminationPlanForMsg(colAP privdata.CollectionAccessPolicy, colFilter privdata.Filter, pvtDataMsg *protoext.SignedGossipMessage) ([]*dissemination, error) { 204 var disseminationPlan []*dissemination 205 206 routingFilter, err := d.gossipAdapter.PeerFilter(gossipCommon.ChannelID(d.chainID), func(signature api.PeerSignature) bool { 207 return colFilter(protoutil.SignedData{ 208 Data: signature.Message, 209 Signature: signature.Signature, 210 Identity: []byte(signature.PeerIdentity), 211 }) 212 }) 213 214 if err != nil { 215 logger.Error("Failed to retrieve peer routing filter for channel", d.chainID, ":", err) 216 return nil, err 217 } 218 219 m := pvtDataMsg.GetPrivateData().Payload 220 221 eligiblePeers := d.eligiblePeersOfChannel(routingFilter) 222 identitySets := d.identitiesOfEligiblePeers(eligiblePeers, colAP) 223 224 peerEndpoints := map[string]string{} 225 for _, peer := range eligiblePeers { 226 epToAdd := peer.Endpoint 227 if epToAdd == "" { 228 epToAdd = peer.InternalEndpoint 229 } 230 peerEndpoints[string(peer.PKIid)] = epToAdd 231 } 232 233 maximumPeerCount := colAP.MaximumPeerCount() 234 requiredPeerCount := colAP.RequiredPeerCount() 235 236 remainingPeers := []api.PeerIdentityInfo{} 237 selectedPeerEndpoints := []string{} 238 239 rand.Seed(time.Now().Unix()) 240 // Select one representative from each org 241 if maximumPeerCount > 0 { 242 for _, selectionPeers := range identitySets { 243 required := 1 244 if requiredPeerCount == 0 { 245 required = 0 246 } 247 selectedPeerIndex := rand.Intn(len(selectionPeers)) 248 peer2SendPerOrg := selectionPeers[selectedPeerIndex] 249 selectedPeerEndpoints = append(selectedPeerEndpoints, peerEndpoints[string(peer2SendPerOrg.PKIId)]) 250 sc := gossipgossip.SendCriteria{ 251 Timeout: d.pushAckTimeout, 252 Channel: gossipCommon.ChannelID(d.chainID), 253 MaxPeers: 1, 254 MinAck: required, 255 IsEligible: func(member discovery.NetworkMember) bool { 256 return bytes.Equal(member.PKIid, peer2SendPerOrg.PKIId) 257 }, 258 } 259 disseminationPlan = append(disseminationPlan, &dissemination{ 260 criteria: sc, 261 msg: &protoext.SignedGossipMessage{ 262 Envelope: proto.Clone(pvtDataMsg.Envelope).(*protosgossip.Envelope), 263 GossipMessage: proto.Clone(pvtDataMsg.GossipMessage).(*protosgossip.GossipMessage), 264 }, 265 }) 266 267 // Add unselected peers to remainingPeers 268 for i, peer := range selectionPeers { 269 if i != selectedPeerIndex { 270 remainingPeers = append(remainingPeers, peer) 271 } 272 } 273 274 if requiredPeerCount > 0 { 275 requiredPeerCount-- 276 } 277 278 maximumPeerCount-- 279 if maximumPeerCount == 0 { 280 logger.Debug("MaximumPeerCount satisfied") 281 logger.Debugf("Disseminating private RWSet for TxID [%s] namespace [%s] collection [%s] to peers: %v", m.TxId, m.Namespace, m.CollectionName, selectedPeerEndpoints) 282 return disseminationPlan, nil 283 } 284 } 285 } 286 287 // criteria to select remaining peers to satisfy colAP.MaximumPeerCount() if there are still 288 // unselected peers remaining for dissemination 289 numPeersToSelect := maximumPeerCount 290 if len(remainingPeers) < maximumPeerCount { 291 numPeersToSelect = len(remainingPeers) 292 } 293 if numPeersToSelect > 0 { 294 logger.Debugf("MaximumPeerCount not satisfied, selecting %d more peer(s) for dissemination", numPeersToSelect) 295 } 296 for maximumPeerCount > 0 && len(remainingPeers) > 0 { 297 required := 1 298 if requiredPeerCount == 0 { 299 required = 0 300 } 301 selectedPeerIndex := rand.Intn(len(remainingPeers)) 302 peer2Send := remainingPeers[selectedPeerIndex] 303 selectedPeerEndpoints = append(selectedPeerEndpoints, peerEndpoints[string(peer2Send.PKIId)]) 304 sc := gossipgossip.SendCriteria{ 305 Timeout: d.pushAckTimeout, 306 Channel: gossipCommon.ChannelID(d.chainID), 307 MaxPeers: 1, 308 MinAck: required, 309 IsEligible: func(member discovery.NetworkMember) bool { 310 return bytes.Equal(member.PKIid, peer2Send.PKIId) 311 }, 312 } 313 disseminationPlan = append(disseminationPlan, &dissemination{ 314 criteria: sc, 315 msg: &protoext.SignedGossipMessage{ 316 Envelope: proto.Clone(pvtDataMsg.Envelope).(*protosgossip.Envelope), 317 GossipMessage: proto.Clone(pvtDataMsg.GossipMessage).(*protosgossip.GossipMessage), 318 }, 319 }) 320 if requiredPeerCount > 0 { 321 requiredPeerCount-- 322 } 323 324 maximumPeerCount-- 325 326 // remove the selected peer from remaining peers 327 remainingPeers = append(remainingPeers[:selectedPeerIndex], remainingPeers[selectedPeerIndex+1:]...) 328 } 329 330 logger.Debugf("Disseminating private RWSet for TxID [%s] namespace [%s] collection [%s] to peers: %v", m.TxId, m.Namespace, m.CollectionName, selectedPeerEndpoints) 331 return disseminationPlan, nil 332 } 333 334 func (d *distributorImpl) identitiesOfEligiblePeers(eligiblePeers []discovery.NetworkMember, colAP privdata.CollectionAccessPolicy) map[string]api.PeerIdentitySet { 335 return d.gossipAdapter.IdentityInfo(). 336 Filter(func(info api.PeerIdentityInfo) bool { 337 for _, orgID := range colAP.MemberOrgs() { 338 if bytes.Equal(info.Organization, []byte(orgID)) { 339 return true 340 } 341 } 342 // peer not in the org 343 return false 344 }).Filter(func(info api.PeerIdentityInfo) bool { 345 for _, peer := range eligiblePeers { 346 if bytes.Equal(info.PKIId, peer.PKIid) { 347 return true 348 } 349 } 350 // peer not in the channel 351 return false 352 }).ByOrg() 353 } 354 355 func (d *distributorImpl) eligiblePeersOfChannel(routingFilter filter.RoutingFilter) []discovery.NetworkMember { 356 var eligiblePeers []discovery.NetworkMember 357 for _, peer := range d.gossipAdapter.PeersOfChannel(gossipCommon.ChannelID(d.chainID)) { 358 if routingFilter(peer) { 359 eligiblePeers = append(eligiblePeers, peer) 360 } 361 } 362 return eligiblePeers 363 } 364 365 func (d *distributorImpl) disseminate(disseminationPlan []*dissemination) error { 366 var failures uint32 367 var wg sync.WaitGroup 368 wg.Add(len(disseminationPlan)) 369 start := time.Now() 370 for _, dis := range disseminationPlan { 371 go func(dis *dissemination) { 372 defer wg.Done() 373 defer d.reportSendDuration(start) 374 err := d.SendByCriteria(dis.msg, dis.criteria) 375 if err != nil { 376 atomic.AddUint32(&failures, 1) 377 m := dis.msg.GetPrivateData().Payload 378 logger.Error("Failed disseminating private RWSet for TxID", m.TxId, ", namespace", m.Namespace, "collection", m.CollectionName, ":", err) 379 } 380 }(dis) 381 } 382 wg.Wait() 383 failureCount := atomic.LoadUint32(&failures) 384 if failureCount != 0 { 385 return errors.Errorf("Failed disseminating %d out of %d private dissemination plans", failureCount, len(disseminationPlan)) 386 } 387 return nil 388 } 389 390 func (d *distributorImpl) reportSendDuration(startTime time.Time) { 391 d.metrics.SendDuration.With("channel", d.chainID).Observe(time.Since(startTime).Seconds()) 392 } 393 394 func (d *distributorImpl) createPrivateDataMessage(txID, namespace string, 395 collection *rwset.CollectionPvtReadWriteSet, 396 ccp *peer.CollectionConfigPackage, 397 blkHt uint64) (*protoext.SignedGossipMessage, error) { 398 msg := &protosgossip.GossipMessage{ 399 Channel: []byte(d.chainID), 400 Nonce: util.RandomUInt64(), 401 Tag: protosgossip.GossipMessage_CHAN_ONLY, 402 Content: &protosgossip.GossipMessage_PrivateData{ 403 PrivateData: &protosgossip.PrivateDataMessage{ 404 Payload: &protosgossip.PrivatePayload{ 405 Namespace: namespace, 406 CollectionName: collection.CollectionName, 407 TxId: txID, 408 PrivateRwset: collection.Rwset, 409 PrivateSimHeight: blkHt, 410 CollectionConfigs: ccp, 411 }, 412 }, 413 }, 414 } 415 416 pvtDataMsg, err := protoext.NoopSign(msg) 417 if err != nil { 418 return nil, err 419 } 420 return pvtDataMsg, nil 421 }