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  }