github.com/kaituanwang/hyperledger@v2.0.1+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.WithMessagef(err, "could not build private data dissemination plan for chaincode %s and collection %s", namespace, collectionName)
   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  
   223  	// With the shift to per peer dissemination in FAB-15389, we must first check
   224  	// that there are enough eligible peers to satisfy RequiredPeerCount.
   225  	if (len(eligiblePeers)) < colAP.RequiredPeerCount() {
   226  		return nil, errors.Errorf("required to disseminate to at least %d peers, but know of only %d eligible peers", colAP.RequiredPeerCount(), len(eligiblePeers))
   227  	}
   228  
   229  	// Group eligible peers by org so that we can disseminate across orgs first
   230  	identitySetsByOrg := d.identitiesOfEligiblePeersByOrg(eligiblePeers, colAP)
   231  
   232  	// peerEndpoints are used for dissemination debug only
   233  	peerEndpoints := map[string]string{}
   234  	for _, peer := range eligiblePeers {
   235  		epToAdd := peer.Endpoint
   236  		if epToAdd == "" {
   237  			epToAdd = peer.InternalEndpoint
   238  		}
   239  		peerEndpoints[string(peer.PKIid)] = epToAdd
   240  	}
   241  
   242  	// Initialize maximumPeerRemainingCount and requiredPeerRemainingCount,
   243  	// these will be decremented until we've selected enough peers for dissemination
   244  	maximumPeerRemainingCount := colAP.MaximumPeerCount()
   245  	requiredPeerRemainingCount := colAP.RequiredPeerCount()
   246  
   247  	remainingPeersAcrossOrgs := []api.PeerIdentityInfo{}
   248  	selectedPeerEndpointsForDebug := []string{}
   249  
   250  	rand.Seed(time.Now().Unix())
   251  
   252  	// PHASE 1 - Select one peer from each eligible org
   253  	if maximumPeerRemainingCount > 0 {
   254  		for _, selectionPeersForOrg := range identitySetsByOrg {
   255  
   256  			// Peers are tagged as a required peer (acksRequired=1) for RequiredPeerCount up front before dissemination.
   257  			// TODO It would be better to attempt dissemination to MaxPeerCount first, and then verify that enough sends were acknowledged to meet RequiredPeerCount.
   258  			acksRequired := 1
   259  			if requiredPeerRemainingCount == 0 {
   260  				acksRequired = 0
   261  			}
   262  
   263  			selectedPeerIndex := rand.Intn(len(selectionPeersForOrg))
   264  			peer2SendPerOrg := selectionPeersForOrg[selectedPeerIndex]
   265  			selectedPeerEndpointsForDebug = append(selectedPeerEndpointsForDebug, peerEndpoints[string(peer2SendPerOrg.PKIId)])
   266  			sc := gossipgossip.SendCriteria{
   267  				Timeout:  d.pushAckTimeout,
   268  				Channel:  gossipCommon.ChannelID(d.chainID),
   269  				MaxPeers: 1,
   270  				MinAck:   acksRequired,
   271  				IsEligible: func(member discovery.NetworkMember) bool {
   272  					return bytes.Equal(member.PKIid, peer2SendPerOrg.PKIId)
   273  				},
   274  			}
   275  			disseminationPlan = append(disseminationPlan, &dissemination{
   276  				criteria: sc,
   277  				msg: &protoext.SignedGossipMessage{
   278  					Envelope:      proto.Clone(pvtDataMsg.Envelope).(*protosgossip.Envelope),
   279  					GossipMessage: proto.Clone(pvtDataMsg.GossipMessage).(*protosgossip.GossipMessage),
   280  				},
   281  			})
   282  
   283  			// Add unselected peers to remainingPeersAcrossOrgs
   284  			for i, peer := range selectionPeersForOrg {
   285  				if i != selectedPeerIndex {
   286  					remainingPeersAcrossOrgs = append(remainingPeersAcrossOrgs, peer)
   287  				}
   288  			}
   289  
   290  			if requiredPeerRemainingCount > 0 {
   291  				requiredPeerRemainingCount--
   292  			}
   293  
   294  			maximumPeerRemainingCount--
   295  			if maximumPeerRemainingCount == 0 {
   296  				logger.Debug("MaximumPeerCount satisfied")
   297  				logger.Debugf("Disseminating private RWSet for TxID [%s] namespace [%s] collection [%s] to peers: %v", m.TxId, m.Namespace, m.CollectionName, selectedPeerEndpointsForDebug)
   298  				return disseminationPlan, nil
   299  			}
   300  		}
   301  	}
   302  
   303  	// PHASE 2 - Select additional peers to satisfy colAP.MaximumPeerCount() if there are still peers in the remainingPeersAcrossOrgs pool
   304  	numRemainingPeersToSelect := maximumPeerRemainingCount
   305  	if len(remainingPeersAcrossOrgs) < maximumPeerRemainingCount {
   306  		numRemainingPeersToSelect = len(remainingPeersAcrossOrgs)
   307  	}
   308  	if numRemainingPeersToSelect > 0 {
   309  		logger.Debugf("MaximumPeerCount not yet satisfied after picking one peer per org, selecting %d more peer(s) for dissemination", numRemainingPeersToSelect)
   310  	}
   311  	for maximumPeerRemainingCount > 0 && len(remainingPeersAcrossOrgs) > 0 {
   312  		required := 1
   313  		if requiredPeerRemainingCount == 0 {
   314  			required = 0
   315  		}
   316  		selectedPeerIndex := rand.Intn(len(remainingPeersAcrossOrgs))
   317  		peer2Send := remainingPeersAcrossOrgs[selectedPeerIndex]
   318  		selectedPeerEndpointsForDebug = append(selectedPeerEndpointsForDebug, peerEndpoints[string(peer2Send.PKIId)])
   319  		sc := gossipgossip.SendCriteria{
   320  			Timeout:  d.pushAckTimeout,
   321  			Channel:  gossipCommon.ChannelID(d.chainID),
   322  			MaxPeers: 1,
   323  			MinAck:   required,
   324  			IsEligible: func(member discovery.NetworkMember) bool {
   325  				return bytes.Equal(member.PKIid, peer2Send.PKIId)
   326  			},
   327  		}
   328  		disseminationPlan = append(disseminationPlan, &dissemination{
   329  			criteria: sc,
   330  			msg: &protoext.SignedGossipMessage{
   331  				Envelope:      proto.Clone(pvtDataMsg.Envelope).(*protosgossip.Envelope),
   332  				GossipMessage: proto.Clone(pvtDataMsg.GossipMessage).(*protosgossip.GossipMessage),
   333  			},
   334  		})
   335  		if requiredPeerRemainingCount > 0 {
   336  			requiredPeerRemainingCount--
   337  		}
   338  
   339  		maximumPeerRemainingCount--
   340  
   341  		// remove the selected peer from remaining peers
   342  		remainingPeersAcrossOrgs = append(remainingPeersAcrossOrgs[:selectedPeerIndex], remainingPeersAcrossOrgs[selectedPeerIndex+1:]...)
   343  	}
   344  
   345  	logger.Debugf("Disseminating private RWSet for TxID [%s] namespace [%s] collection [%s] to peers: %v", m.TxId, m.Namespace, m.CollectionName, selectedPeerEndpointsForDebug)
   346  	return disseminationPlan, nil
   347  }
   348  
   349  // identitiesOfEligiblePeersByOrg returns the peers eligible for a collection (aka PeerIdentitySet) grouped in a hash map keyed by orgid
   350  func (d *distributorImpl) identitiesOfEligiblePeersByOrg(eligiblePeers []discovery.NetworkMember, colAP privdata.CollectionAccessPolicy) map[string]api.PeerIdentitySet {
   351  	return d.gossipAdapter.IdentityInfo().
   352  		Filter(func(info api.PeerIdentityInfo) bool {
   353  			if _, ok := colAP.MemberOrgs()[string(info.Organization)]; ok {
   354  				return true
   355  			}
   356  			// peer not in the org
   357  			return false
   358  		}).Filter(func(info api.PeerIdentityInfo) bool {
   359  		for _, peer := range eligiblePeers {
   360  			if bytes.Equal(info.PKIId, peer.PKIid) {
   361  				return true
   362  			}
   363  		}
   364  		// peer not in the channel
   365  		return false
   366  	}).ByOrg()
   367  }
   368  
   369  func (d *distributorImpl) eligiblePeersOfChannel(routingFilter filter.RoutingFilter) []discovery.NetworkMember {
   370  	var eligiblePeers []discovery.NetworkMember
   371  	for _, peer := range d.gossipAdapter.PeersOfChannel(gossipCommon.ChannelID(d.chainID)) {
   372  		if routingFilter(peer) {
   373  			eligiblePeers = append(eligiblePeers, peer)
   374  		}
   375  	}
   376  	return eligiblePeers
   377  }
   378  
   379  func (d *distributorImpl) disseminate(disseminationPlan []*dissemination) error {
   380  	var failures uint32
   381  	var wg sync.WaitGroup
   382  	wg.Add(len(disseminationPlan))
   383  	start := time.Now()
   384  	for _, dis := range disseminationPlan {
   385  		go func(dis *dissemination) {
   386  			defer wg.Done()
   387  			defer d.reportSendDuration(start)
   388  			err := d.SendByCriteria(dis.msg, dis.criteria)
   389  			if err != nil {
   390  				atomic.AddUint32(&failures, 1)
   391  				m := dis.msg.GetPrivateData().Payload
   392  				logger.Error("Failed disseminating private RWSet for TxID", m.TxId, ", namespace", m.Namespace, "collection", m.CollectionName, ":", err)
   393  			}
   394  		}(dis)
   395  	}
   396  	wg.Wait()
   397  	failureCount := atomic.LoadUint32(&failures)
   398  	if failureCount != 0 {
   399  		return errors.Errorf("Failed disseminating %d out of %d private dissemination plans", failureCount, len(disseminationPlan))
   400  	}
   401  	return nil
   402  }
   403  
   404  func (d *distributorImpl) reportSendDuration(startTime time.Time) {
   405  	d.metrics.SendDuration.With("channel", d.chainID).Observe(time.Since(startTime).Seconds())
   406  }
   407  
   408  func (d *distributorImpl) createPrivateDataMessage(txID, namespace string,
   409  	collection *rwset.CollectionPvtReadWriteSet,
   410  	ccp *peer.CollectionConfigPackage,
   411  	blkHt uint64) (*protoext.SignedGossipMessage, error) {
   412  	msg := &protosgossip.GossipMessage{
   413  		Channel: []byte(d.chainID),
   414  		Nonce:   util.RandomUInt64(),
   415  		Tag:     protosgossip.GossipMessage_CHAN_ONLY,
   416  		Content: &protosgossip.GossipMessage_PrivateData{
   417  			PrivateData: &protosgossip.PrivateDataMessage{
   418  				Payload: &protosgossip.PrivatePayload{
   419  					Namespace:         namespace,
   420  					CollectionName:    collection.CollectionName,
   421  					TxId:              txID,
   422  					PrivateRwset:      collection.Rwset,
   423  					PrivateSimHeight:  blkHt,
   424  					CollectionConfigs: ccp,
   425  				},
   426  			},
   427  		},
   428  	}
   429  
   430  	pvtDataMsg, err := protoext.NoopSign(msg)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	return pvtDataMsg, nil
   435  }