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 }