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  }