github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/gossip/privdata/distributor.go (about) 1 /* 2 Copyright hechain. 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 "github.com/hechain20/hechain/core/common/privdata" 19 "github.com/hechain20/hechain/gossip/api" 20 gossipCommon "github.com/hechain20/hechain/gossip/common" 21 "github.com/hechain20/hechain/gossip/discovery" 22 "github.com/hechain20/hechain/gossip/filter" 23 gossipgossip "github.com/hechain20/hechain/gossip/gossip" 24 "github.com/hechain20/hechain/gossip/metrics" 25 "github.com/hechain20/hechain/gossip/protoext" 26 "github.com/hechain20/hechain/gossip/util" 27 "github.com/hechain20/hechain/msp" 28 "github.com/hechain20/hechain/protoutil" 29 protosgossip "github.com/hyperledger/fabric-protos-go/gossip" 30 "github.com/hyperledger/fabric-protos-go/ledger/rwset" 31 "github.com/hyperledger/fabric-protos-go/peer" 32 "github.com/hyperledger/fabric-protos-go/transientstore" 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 logger util.Logger 82 metrics *metrics.PrivdataMetrics 83 } 84 85 //go:generate mockery -dir . -name CollectionAccessFactory -case underscore -output ./mocks/ 86 87 // CollectionAccessFactory an interface to generate collection access policy 88 type CollectionAccessFactory interface { 89 // AccessPolicy based on collection configuration 90 AccessPolicy(config *peer.CollectionConfig, chainID string) (privdata.CollectionAccessPolicy, error) 91 } 92 93 //go:generate mockery -dir . -name CollectionAccessPolicy -case underscore -output ./mocks/ 94 95 type CollectionAccessPolicy interface { 96 privdata.CollectionAccessPolicy 97 } 98 99 // policyAccessFactory the implementation of CollectionAccessFactory 100 type policyAccessFactory struct { 101 IdentityDeserializerFactory 102 } 103 104 func (p *policyAccessFactory) AccessPolicy(config *peer.CollectionConfig, chainID string) (privdata.CollectionAccessPolicy, error) { 105 colAP := &privdata.SimpleCollection{} 106 switch cconf := config.Payload.(type) { 107 case *peer.CollectionConfig_StaticCollectionConfig: 108 err := colAP.Setup(cconf.StaticCollectionConfig, p.GetIdentityDeserializer(chainID)) 109 if err != nil { 110 return nil, errors.WithMessagef(err, "error setting up collection %#v", cconf.StaticCollectionConfig.Name) 111 } 112 default: 113 return nil, errors.New("unexpected collection type") 114 } 115 return colAP, nil 116 } 117 118 // NewCollectionAccessFactory 119 func NewCollectionAccessFactory(factory IdentityDeserializerFactory) CollectionAccessFactory { 120 return &policyAccessFactory{ 121 IdentityDeserializerFactory: factory, 122 } 123 } 124 125 // NewDistributor a constructor for private data distributor capable to send 126 // private read write sets for underlying collection 127 func NewDistributor(chainID string, gossip gossipAdapter, factory CollectionAccessFactory, 128 metrics *metrics.PrivdataMetrics, pushAckTimeout time.Duration) PvtDataDistributor { 129 return &distributorImpl{ 130 chainID: chainID, 131 gossipAdapter: gossip, 132 CollectionAccessFactory: factory, 133 pushAckTimeout: pushAckTimeout, 134 logger: logger.With("channel", chainID), 135 metrics: metrics, 136 } 137 } 138 139 // Distribute broadcast reliably private data read write set based on policies 140 func (d *distributorImpl) Distribute(txID string, privData *transientstore.TxPvtReadWriteSetWithConfigInfo, blkHt uint64) error { 141 disseminationPlan, err := d.computeDisseminationPlan(txID, privData, blkHt) 142 if err != nil { 143 return errors.WithStack(err) 144 } 145 return d.disseminate(disseminationPlan) 146 } 147 148 type dissemination struct { 149 msg *protoext.SignedGossipMessage 150 criteria gossipgossip.SendCriteria 151 } 152 153 func (d *distributorImpl) computeDisseminationPlan(txID string, 154 privDataWithConfig *transientstore.TxPvtReadWriteSetWithConfigInfo, 155 blkHt uint64) ([]*dissemination, error) { 156 privData := privDataWithConfig.PvtRwset 157 var disseminationPlan []*dissemination 158 for _, pvtRwset := range privData.NsPvtRwset { 159 namespace := pvtRwset.Namespace 160 configPackage, found := privDataWithConfig.CollectionConfigs[namespace] 161 if !found { 162 d.logger.Error("Collection config package for", namespace, "chaincode is not provided") 163 return nil, errors.New(fmt.Sprint("collection config package for", namespace, "chaincode is not provided")) 164 } 165 166 for _, collection := range pvtRwset.CollectionPvtRwset { 167 colCP, err := d.getCollectionConfig(configPackage, collection) 168 collectionName := collection.CollectionName 169 if err != nil { 170 d.logger.Error("Could not find collection access policy for", namespace, " and collection", collectionName, "error", err) 171 return nil, errors.WithMessage(err, fmt.Sprint("could not find collection access policy for", namespace, " and collection", collectionName, "error", err)) 172 } 173 174 colAP, err := d.AccessPolicy(colCP, d.chainID) 175 if err != nil { 176 d.logger.Error("Could not obtain collection access policy, collection name", collectionName, "due to", err) 177 return nil, errors.Wrap(err, fmt.Sprint("Could not obtain collection access policy, collection name", collectionName, "due to", err)) 178 } 179 180 colFilter := colAP.AccessFilter() 181 if colFilter == nil { 182 d.logger.Error("Collection access policy for", collectionName, "has no filter") 183 return nil, errors.Errorf("No collection access policy filter computed for %v", collectionName) 184 } 185 186 pvtDataMsg, err := d.createPrivateDataMessage(txID, namespace, collection, &peer.CollectionConfigPackage{Config: []*peer.CollectionConfig{colCP}}, blkHt) 187 if err != nil { 188 return nil, errors.WithStack(err) 189 } 190 191 d.logger.Debugf("Computing dissemination plan for collection [%s]", collectionName) 192 dPlan, err := d.disseminationPlanForMsg(colAP, colFilter, pvtDataMsg) 193 if err != nil { 194 return nil, errors.WithMessagef(err, "could not build private data dissemination plan for chaincode %s and collection %s", namespace, collectionName) 195 } 196 disseminationPlan = append(disseminationPlan, dPlan...) 197 } 198 } 199 return disseminationPlan, nil 200 } 201 202 func (d *distributorImpl) getCollectionConfig(config *peer.CollectionConfigPackage, collection *rwset.CollectionPvtReadWriteSet) (*peer.CollectionConfig, error) { 203 for _, c := range config.Config { 204 if staticConfig := c.GetStaticCollectionConfig(); staticConfig != nil { 205 if staticConfig.Name == collection.CollectionName { 206 return c, nil 207 } 208 } 209 } 210 return nil, errors.New(fmt.Sprint("no configuration for collection", collection.CollectionName, "found")) 211 } 212 213 func (d *distributorImpl) disseminationPlanForMsg(colAP privdata.CollectionAccessPolicy, colFilter privdata.Filter, pvtDataMsg *protoext.SignedGossipMessage) ([]*dissemination, error) { 214 var disseminationPlan []*dissemination 215 216 routingFilter, err := d.gossipAdapter.PeerFilter(gossipCommon.ChannelID(d.chainID), func(signature api.PeerSignature) bool { 217 return colFilter(protoutil.SignedData{ 218 Data: signature.Message, 219 Signature: signature.Signature, 220 Identity: []byte(signature.PeerIdentity), 221 }) 222 }) 223 if err != nil { 224 d.logger.Error("Failed to retrieve peer routing filter for channel", d.chainID, ":", err) 225 return nil, err 226 } 227 228 m := pvtDataMsg.GetPrivateData().Payload 229 230 eligiblePeers := d.eligiblePeersOfChannel(routingFilter) 231 232 // With the shift to per peer dissemination in FAB-15389, we must first check 233 // that there are enough eligible peers to satisfy RequiredPeerCount. 234 if (len(eligiblePeers)) < colAP.RequiredPeerCount() { 235 return nil, errors.Errorf("required to disseminate to at least %d peers, but know of only %d eligible peers", colAP.RequiredPeerCount(), len(eligiblePeers)) 236 } 237 238 // Group eligible peers by org so that we can disseminate across orgs first 239 identitySetsByOrg := d.identitiesOfEligiblePeersByOrg(eligiblePeers, colAP) 240 241 // peerEndpoints are used for dissemination debug only 242 peerEndpoints := map[string]string{} 243 for _, peer := range eligiblePeers { 244 epToAdd := peer.Endpoint 245 if epToAdd == "" { 246 epToAdd = peer.InternalEndpoint 247 } 248 peerEndpoints[string(peer.PKIid)] = epToAdd 249 } 250 251 // Initialize maximumPeerRemainingCount and requiredPeerRemainingCount, 252 // these will be decremented until we've selected enough peers for dissemination 253 maximumPeerRemainingCount := colAP.MaximumPeerCount() 254 requiredPeerRemainingCount := colAP.RequiredPeerCount() 255 256 remainingPeersAcrossOrgs := []api.PeerIdentityInfo{} 257 selectedPeerEndpointsForDebug := []string{} 258 259 rand.Seed(time.Now().Unix()) 260 261 // PHASE 1 - Select one peer from each eligible org 262 if maximumPeerRemainingCount > 0 { 263 for _, selectionPeersForOrg := range identitySetsByOrg { 264 265 // Peers are tagged as a required peer (acksRequired=1) for RequiredPeerCount up front before dissemination. 266 // TODO It would be better to attempt dissemination to MaxPeerCount first, and then verify that enough sends were acknowledged to meet RequiredPeerCount. 267 acksRequired := 1 268 if requiredPeerRemainingCount == 0 { 269 acksRequired = 0 270 } 271 272 selectedPeerIndex := rand.Intn(len(selectionPeersForOrg)) 273 peer2SendPerOrg := selectionPeersForOrg[selectedPeerIndex] 274 selectedPeerEndpointsForDebug = append(selectedPeerEndpointsForDebug, peerEndpoints[string(peer2SendPerOrg.PKIId)]) 275 sc := gossipgossip.SendCriteria{ 276 Timeout: d.pushAckTimeout, 277 Channel: gossipCommon.ChannelID(d.chainID), 278 MaxPeers: 1, 279 MinAck: acksRequired, 280 IsEligible: func(member discovery.NetworkMember) bool { 281 return bytes.Equal(member.PKIid, peer2SendPerOrg.PKIId) 282 }, 283 } 284 disseminationPlan = append(disseminationPlan, &dissemination{ 285 criteria: sc, 286 msg: &protoext.SignedGossipMessage{ 287 Envelope: proto.Clone(pvtDataMsg.Envelope).(*protosgossip.Envelope), 288 GossipMessage: proto.Clone(pvtDataMsg.GossipMessage).(*protosgossip.GossipMessage), 289 }, 290 }) 291 292 // Add unselected peers to remainingPeersAcrossOrgs 293 for i, peer := range selectionPeersForOrg { 294 if i != selectedPeerIndex { 295 remainingPeersAcrossOrgs = append(remainingPeersAcrossOrgs, peer) 296 } 297 } 298 299 if requiredPeerRemainingCount > 0 { 300 requiredPeerRemainingCount-- 301 } 302 303 maximumPeerRemainingCount-- 304 if maximumPeerRemainingCount == 0 { 305 d.logger.Debug("MaximumPeerCount satisfied") 306 d.logger.Debugf("Disseminating private RWSet for TxID [%s] namespace [%s] collection [%s] to peers: %v", m.TxId, m.Namespace, m.CollectionName, selectedPeerEndpointsForDebug) 307 return disseminationPlan, nil 308 } 309 } 310 } 311 312 // PHASE 2 - Select additional peers to satisfy colAP.MaximumPeerCount() if there are still peers in the remainingPeersAcrossOrgs pool 313 numRemainingPeersToSelect := maximumPeerRemainingCount 314 if len(remainingPeersAcrossOrgs) < maximumPeerRemainingCount { 315 numRemainingPeersToSelect = len(remainingPeersAcrossOrgs) 316 } 317 if numRemainingPeersToSelect > 0 { 318 d.logger.Debugf("MaximumPeerCount not yet satisfied after picking one peer per org, selecting %d more peer(s) for dissemination", numRemainingPeersToSelect) 319 } 320 for maximumPeerRemainingCount > 0 && len(remainingPeersAcrossOrgs) > 0 { 321 required := 1 322 if requiredPeerRemainingCount == 0 { 323 required = 0 324 } 325 selectedPeerIndex := rand.Intn(len(remainingPeersAcrossOrgs)) 326 peer2Send := remainingPeersAcrossOrgs[selectedPeerIndex] 327 selectedPeerEndpointsForDebug = append(selectedPeerEndpointsForDebug, peerEndpoints[string(peer2Send.PKIId)]) 328 sc := gossipgossip.SendCriteria{ 329 Timeout: d.pushAckTimeout, 330 Channel: gossipCommon.ChannelID(d.chainID), 331 MaxPeers: 1, 332 MinAck: required, 333 IsEligible: func(member discovery.NetworkMember) bool { 334 return bytes.Equal(member.PKIid, peer2Send.PKIId) 335 }, 336 } 337 disseminationPlan = append(disseminationPlan, &dissemination{ 338 criteria: sc, 339 msg: &protoext.SignedGossipMessage{ 340 Envelope: proto.Clone(pvtDataMsg.Envelope).(*protosgossip.Envelope), 341 GossipMessage: proto.Clone(pvtDataMsg.GossipMessage).(*protosgossip.GossipMessage), 342 }, 343 }) 344 if requiredPeerRemainingCount > 0 { 345 requiredPeerRemainingCount-- 346 } 347 348 maximumPeerRemainingCount-- 349 350 // remove the selected peer from remaining peers 351 remainingPeersAcrossOrgs = append(remainingPeersAcrossOrgs[:selectedPeerIndex], remainingPeersAcrossOrgs[selectedPeerIndex+1:]...) 352 } 353 354 d.logger.Debugf("Disseminating private RWSet for TxID [%s] namespace [%s] collection [%s] to peers: %v", m.TxId, m.Namespace, m.CollectionName, selectedPeerEndpointsForDebug) 355 return disseminationPlan, nil 356 } 357 358 // identitiesOfEligiblePeersByOrg returns the peers eligible for a collection (aka PeerIdentitySet) grouped in a hash map keyed by orgid 359 func (d *distributorImpl) identitiesOfEligiblePeersByOrg(eligiblePeers []discovery.NetworkMember, colAP privdata.CollectionAccessPolicy) map[string]api.PeerIdentitySet { 360 return d.gossipAdapter.IdentityInfo(). 361 Filter(func(info api.PeerIdentityInfo) bool { 362 if _, ok := colAP.MemberOrgs()[string(info.Organization)]; ok { 363 return true 364 } 365 // peer not in the org 366 return false 367 }).Filter(func(info api.PeerIdentityInfo) bool { 368 for _, peer := range eligiblePeers { 369 if bytes.Equal(info.PKIId, peer.PKIid) { 370 return true 371 } 372 } 373 // peer not in the channel 374 return false 375 }).ByOrg() 376 } 377 378 func (d *distributorImpl) eligiblePeersOfChannel(routingFilter filter.RoutingFilter) []discovery.NetworkMember { 379 var eligiblePeers []discovery.NetworkMember 380 for _, peer := range d.gossipAdapter.PeersOfChannel(gossipCommon.ChannelID(d.chainID)) { 381 if routingFilter(peer) { 382 eligiblePeers = append(eligiblePeers, peer) 383 } 384 } 385 return eligiblePeers 386 } 387 388 func (d *distributorImpl) disseminate(disseminationPlan []*dissemination) error { 389 var failures uint32 390 var wg sync.WaitGroup 391 wg.Add(len(disseminationPlan)) 392 start := time.Now() 393 for _, dis := range disseminationPlan { 394 go func(dis *dissemination) { 395 defer wg.Done() 396 defer d.reportSendDuration(start) 397 err := d.SendByCriteria(dis.msg, dis.criteria) 398 if err != nil { 399 atomic.AddUint32(&failures, 1) 400 m := dis.msg.GetPrivateData().Payload 401 d.logger.Error("Failed disseminating private RWSet for TxID", m.TxId, ", namespace", m.Namespace, "collection", m.CollectionName, ":", err) 402 } 403 }(dis) 404 } 405 wg.Wait() 406 failureCount := atomic.LoadUint32(&failures) 407 if failureCount != 0 { 408 return errors.Errorf("Failed disseminating %d out of %d private dissemination plans", failureCount, len(disseminationPlan)) 409 } 410 return nil 411 } 412 413 func (d *distributorImpl) reportSendDuration(startTime time.Time) { 414 d.metrics.SendDuration.With("channel", d.chainID).Observe(time.Since(startTime).Seconds()) 415 } 416 417 func (d *distributorImpl) createPrivateDataMessage(txID, namespace string, 418 collection *rwset.CollectionPvtReadWriteSet, 419 ccp *peer.CollectionConfigPackage, 420 blkHt uint64) (*protoext.SignedGossipMessage, error) { 421 msg := &protosgossip.GossipMessage{ 422 Channel: []byte(d.chainID), 423 Nonce: util.RandomUInt64(), 424 Tag: protosgossip.GossipMessage_CHAN_ONLY, 425 Content: &protosgossip.GossipMessage_PrivateData{ 426 PrivateData: &protosgossip.PrivateDataMessage{ 427 Payload: &protosgossip.PrivatePayload{ 428 Namespace: namespace, 429 CollectionName: collection.CollectionName, 430 TxId: txID, 431 PrivateRwset: collection.Rwset, 432 PrivateSimHeight: blkHt, 433 CollectionConfigs: ccp, 434 }, 435 }, 436 }, 437 } 438 439 pvtDataMsg, err := protoext.NoopSign(msg) 440 if err != nil { 441 return nil, err 442 } 443 return pvtDataMsg, nil 444 }