github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/discovery/service.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package discovery
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"encoding/hex"
    13  	"fmt"
    14  
    15  	"github.com/hechain20/hechain/common/flogging"
    16  	"github.com/hechain20/hechain/common/util"
    17  	"github.com/hechain20/hechain/discovery/protoext"
    18  	common2 "github.com/hechain20/hechain/gossip/common"
    19  	"github.com/hechain20/hechain/protoutil"
    20  	"github.com/hyperledger/fabric-protos-go/discovery"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  var logger = flogging.MustGetLogger("discovery")
    25  
    26  var accessDenied = wrapError(errors.New("access denied"))
    27  
    28  // certHashExtractor extracts the TLS certificate from a given context
    29  // and returns its hash
    30  type certHashExtractor func(ctx context.Context) []byte
    31  
    32  // dispatcher defines a function that dispatches a query
    33  type dispatcher func(q *discovery.Query) *discovery.QueryResult
    34  
    35  type Service struct {
    36  	config             Config
    37  	channelDispatchers map[protoext.QueryType]dispatcher
    38  	localDispatchers   map[protoext.QueryType]dispatcher
    39  	auth               *authCache
    40  	Support
    41  }
    42  
    43  // Config defines the configuration of the discovery service
    44  type Config struct {
    45  	TLS                          bool
    46  	AuthCacheEnabled             bool
    47  	AuthCacheMaxSize             int
    48  	AuthCachePurgeRetentionRatio float64
    49  }
    50  
    51  // String returns a string representation of this Config
    52  func (c Config) String() string {
    53  	if c.AuthCacheEnabled {
    54  		return fmt.Sprintf("TLS: %t, authCacheMaxSize: %d, authCachePurgeRatio: %f", c.TLS, c.AuthCacheMaxSize, c.AuthCachePurgeRetentionRatio)
    55  	}
    56  	return fmt.Sprintf("TLS: %t, auth cache disabled", c.TLS)
    57  }
    58  
    59  // peerMapping maps PKI-IDs to Peers
    60  type peerMapping map[string]*discovery.Peer
    61  
    62  // NewService creates a new discovery service instance
    63  func NewService(config Config, sup Support) *Service {
    64  	s := &Service{
    65  		auth: newAuthCache(sup, authCacheConfig{
    66  			enabled:             config.AuthCacheEnabled,
    67  			maxCacheSize:        config.AuthCacheMaxSize,
    68  			purgeRetentionRatio: config.AuthCachePurgeRetentionRatio,
    69  		}),
    70  		Support: sup,
    71  	}
    72  	s.channelDispatchers = map[protoext.QueryType]dispatcher{
    73  		protoext.ConfigQueryType:         s.configQuery,
    74  		protoext.ChaincodeQueryType:      s.chaincodeQuery,
    75  		protoext.PeerMembershipQueryType: s.channelMembershipResponse,
    76  	}
    77  	s.localDispatchers = map[protoext.QueryType]dispatcher{
    78  		protoext.LocalMembershipQueryType: s.localMembershipResponse,
    79  	}
    80  	logger.Info("Created with config", config)
    81  	return s
    82  }
    83  
    84  func (s *Service) Discover(ctx context.Context, request *discovery.SignedRequest) (*discovery.Response, error) {
    85  	addr := util.ExtractRemoteAddress(ctx)
    86  	req, err := validateStructure(ctx, request, s.config.TLS, util.ExtractCertificateHashFromContext)
    87  	if err != nil {
    88  		logger.Warningf("Request from %s is malformed or invalid: %v", addr, err)
    89  		return nil, err
    90  	}
    91  	logger.Debugf("Processing request from %s: %v", addr, req)
    92  	var res []*discovery.QueryResult
    93  	for _, q := range req.Queries {
    94  		res = append(res, s.processQuery(q, request, req.Authentication.ClientIdentity, addr))
    95  	}
    96  	logger.Debugf("Returning to %s a response containing: %v", addr, res)
    97  	return &discovery.Response{
    98  		Results: res,
    99  	}, nil
   100  }
   101  
   102  func (s *Service) processQuery(query *discovery.Query, request *discovery.SignedRequest, identity []byte, addr string) *discovery.QueryResult {
   103  	if query.Channel != "" && !s.ChannelExists(query.Channel) {
   104  		logger.Warning("got query for channel", query.Channel, "from", addr, "but it doesn't exist")
   105  		return accessDenied
   106  	}
   107  	if err := s.auth.EligibleForService(query.Channel, protoutil.SignedData{
   108  		Data:      request.Payload,
   109  		Signature: request.Signature,
   110  		Identity:  identity,
   111  	}); err != nil {
   112  		logger.Warning("got query for channel", query.Channel, "from", addr, "but it isn't eligible:", err)
   113  		return accessDenied
   114  	}
   115  	return s.dispatch(query)
   116  }
   117  
   118  func (s *Service) dispatch(q *discovery.Query) *discovery.QueryResult {
   119  	dispatchers := s.channelDispatchers
   120  	// Ensure local queries are routed only to channel-less dispatchers
   121  	if q.Channel == "" {
   122  		dispatchers = s.localDispatchers
   123  	}
   124  	dispatchQuery, exists := dispatchers[protoext.GetQueryType(q)]
   125  	if !exists {
   126  		return wrapError(errors.New("unknown or missing request type"))
   127  	}
   128  	return dispatchQuery(q)
   129  }
   130  
   131  func (s *Service) chaincodeQuery(q *discovery.Query) *discovery.QueryResult {
   132  	if err := validateCCQuery(q.GetCcQuery()); err != nil {
   133  		return wrapError(err)
   134  	}
   135  	var descriptors []*discovery.EndorsementDescriptor
   136  	for _, interest := range q.GetCcQuery().Interests {
   137  		desc, err := s.PeersForEndorsement(common2.ChannelID(q.Channel), interest)
   138  		if err != nil {
   139  			logger.Errorf("Failed constructing descriptor for chaincode %s: %v", interest, err)
   140  			return wrapError(errors.Errorf("failed constructing descriptor for %v", interest))
   141  		}
   142  		descriptors = append(descriptors, desc)
   143  	}
   144  
   145  	return &discovery.QueryResult{
   146  		Result: &discovery.QueryResult_CcQueryRes{
   147  			CcQueryRes: &discovery.ChaincodeQueryResult{
   148  				Content: descriptors,
   149  			},
   150  		},
   151  	}
   152  }
   153  
   154  func (s *Service) configQuery(q *discovery.Query) *discovery.QueryResult {
   155  	conf, err := s.Config(q.Channel)
   156  	if err != nil {
   157  		logger.Errorf("Failed fetching config for channel %s: %v", q.Channel, err)
   158  		return wrapError(errors.Errorf("failed fetching config for channel %s", q.Channel))
   159  	}
   160  	return &discovery.QueryResult{
   161  		Result: &discovery.QueryResult_ConfigResult{
   162  			ConfigResult: conf,
   163  		},
   164  	}
   165  }
   166  
   167  func wrapPeerResponse(peersByOrg map[string]*discovery.Peers) *discovery.QueryResult {
   168  	return &discovery.QueryResult{
   169  		Result: &discovery.QueryResult_Members{
   170  			Members: &discovery.PeerMembershipResult{
   171  				PeersByOrg: peersByOrg,
   172  			},
   173  		},
   174  	}
   175  }
   176  
   177  func (s *Service) channelMembershipResponse(q *discovery.Query) *discovery.QueryResult {
   178  	chanPeers, err := s.PeersAuthorizedByCriteria(common2.ChannelID(q.Channel), q.GetPeerQuery().Filter)
   179  	if err != nil {
   180  		return wrapError(err)
   181  	}
   182  	membersByOrgs := make(map[string]*discovery.Peers)
   183  	chanPeerByID := chanPeers.ByID()
   184  	for org, ids2Peers := range s.computeMembership(q) {
   185  		membersByOrgs[org] = &discovery.Peers{}
   186  		for id, peer := range ids2Peers {
   187  			// Check if the peer is in the channel view
   188  			stateInfoMsg, exists := chanPeerByID[string(id)]
   189  			// If the peer isn't in the channel view, skip it and don't include it in the response
   190  			if !exists {
   191  				continue
   192  			}
   193  			peer.StateInfo = stateInfoMsg.Envelope
   194  			membersByOrgs[org].Peers = append(membersByOrgs[org].Peers, peer)
   195  		}
   196  	}
   197  	return wrapPeerResponse(membersByOrgs)
   198  }
   199  
   200  func (s *Service) localMembershipResponse(q *discovery.Query) *discovery.QueryResult {
   201  	membersByOrgs := make(map[string]*discovery.Peers)
   202  	for org, ids2Peers := range s.computeMembership(q) {
   203  		membersByOrgs[org] = &discovery.Peers{}
   204  		for _, peer := range ids2Peers {
   205  			membersByOrgs[org].Peers = append(membersByOrgs[org].Peers, peer)
   206  		}
   207  	}
   208  	return wrapPeerResponse(membersByOrgs)
   209  }
   210  
   211  func (s *Service) computeMembership(_ *discovery.Query) map[string]peerMapping {
   212  	peersByOrg := make(map[string]peerMapping)
   213  	peerAliveInfo := s.Peers().ByID()
   214  	for org, peerIdentities := range s.IdentityInfo().ByOrg() {
   215  		peersForCurrentOrg := make(peerMapping)
   216  		peersByOrg[org] = peersForCurrentOrg
   217  		for _, id := range peerIdentities {
   218  			// Check peer exists in alive membership view
   219  			aliveInfo, exists := peerAliveInfo[string(id.PKIId)]
   220  			if !exists {
   221  				continue
   222  			}
   223  			peersForCurrentOrg[string(id.PKIId)] = &discovery.Peer{
   224  				Identity:       id.Identity,
   225  				MembershipInfo: aliveInfo.Envelope,
   226  			}
   227  		}
   228  	}
   229  	return peersByOrg
   230  }
   231  
   232  // validateStructure validates that the request contains all the needed fields and that they are computed correctly
   233  func validateStructure(ctx context.Context, request *discovery.SignedRequest, tlsEnabled bool, certHashFromContext certHashExtractor) (*discovery.Request, error) {
   234  	if request == nil {
   235  		return nil, errors.New("nil request")
   236  	}
   237  	req, err := protoext.SignedRequestToRequest(request)
   238  	if err != nil {
   239  		return nil, errors.Wrap(err, "failed parsing request")
   240  	}
   241  	if req.Authentication == nil {
   242  		return nil, errors.New("access denied, no authentication info in request")
   243  	}
   244  	if len(req.Authentication.ClientIdentity) == 0 {
   245  		return nil, errors.New("access denied, client identity wasn't supplied")
   246  	}
   247  	if !tlsEnabled {
   248  		return req, nil
   249  	}
   250  	computedHash := certHashFromContext(ctx)
   251  	if len(computedHash) == 0 {
   252  		return nil, errors.New("client didn't send a TLS certificate")
   253  	}
   254  	if !bytes.Equal(computedHash, req.Authentication.ClientTlsCertHash) {
   255  		claimed := hex.EncodeToString(req.Authentication.ClientTlsCertHash)
   256  		logger.Warningf("client claimed TLS hash %s doesn't match computed TLS hash from gRPC stream %s", claimed, hex.EncodeToString(computedHash))
   257  		return nil, errors.New("client claimed TLS hash doesn't match computed TLS hash from gRPC stream")
   258  	}
   259  	return req, nil
   260  }
   261  
   262  func validateCCQuery(ccQuery *discovery.ChaincodeQuery) error {
   263  	if len(ccQuery.Interests) == 0 {
   264  		return errors.New("chaincode query must have at least one chaincode interest")
   265  	}
   266  	for _, interest := range ccQuery.Interests {
   267  		if interest == nil {
   268  			return errors.New("chaincode interest is nil")
   269  		}
   270  		if len(interest.Chaincodes) == 0 {
   271  			return errors.New("chaincode interest must contain at least one chaincode")
   272  		}
   273  		for _, cc := range interest.Chaincodes {
   274  			if cc.Name == "" {
   275  				return errors.New("chaincode name in interest cannot be empty")
   276  			}
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  func wrapError(err error) *discovery.QueryResult {
   283  	return &discovery.QueryResult{
   284  		Result: &discovery.QueryResult_Error{
   285  			Error: &discovery.Error{
   286  				Content: err.Error(),
   287  			},
   288  		},
   289  	}
   290  }