go_study/fabric-main/internal/pkg/gateway/evaluate.go

171 lines
6.6 KiB
Go

/*
Copyright 2021 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package gateway
import (
"context"
"fmt"
"github.com/golang/protobuf/proto"
gp "github.com/hyperledger/fabric-protos-go/gateway"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Evaluate will invoke the transaction function as specified in the SignedProposal
func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*gp.EvaluateResponse, error) {
if request == nil {
return nil, status.Error(codes.InvalidArgument, "an evaluate request is required")
}
signedProposal := request.GetProposedTransaction()
channel, chaincodeID, hasTransientData, err := getChannelAndChaincodeFromSignedProposal(signedProposal)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to unpack transaction proposal: %s", err)
}
err = gs.registry.connectChannelPeers(channel, false)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
}
targetOrgs := request.GetTargetOrganizations()
transientProtected := false
if hasTransientData && targetOrgs == nil {
targetOrgs = []string{gs.registry.localEndorser.mspid}
transientProtected = true
}
plan, err := gs.registry.evaluator(channel, chaincodeID, targetOrgs)
if err != nil {
if transientProtected {
return nil, status.Errorf(codes.FailedPrecondition, "no endorsers found in the gateway's organization; retry specifying target organization(s) to protect transient data: %s", err)
}
return nil, status.Errorf(codes.FailedPrecondition, "%s", err)
}
endorser := plan.endorsers()[0]
var response *peer.Response
var errDetails []proto.Message
for response == nil {
gs.logger.Debugw("Sending to peer:", "channel", channel, "chaincode", chaincodeID, "txID", request.GetTransactionId(), "MSPID", endorser.mspid, "endpoint", endorser.address)
done := make(chan error)
go func() {
defer close(done)
ctx, cancel := context.WithTimeout(ctx, gs.options.EndorsementTimeout)
defer cancel()
pr, err := endorser.client.ProcessProposal(ctx, signedProposal)
code, message, retry, remove := responseStatus(pr, err)
if code == codes.OK {
response = pr.Response
// Prefer result from proposal response as Response.Payload is not required to be transaction result
if result, err := getResultFromProposalResponse(pr); err == nil {
response.Payload = result
} else {
logger.Warnw("Successful proposal response contained no transaction result", "error", err.Error(), "chaincode", chaincodeID, "channel", channel, "txID", request.GetTransactionId(), "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "status", response.GetStatus(), "message", response.GetMessage())
}
} else {
logger.Debugw("Evaluate call to endorser failed", "chaincode", chaincodeID, "channel", channel, "txID", request.GetTransactionId(), "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "error", message)
errDetails = append(errDetails, errorDetail(endorser.endpointConfig, message))
if remove {
gs.registry.removeEndorser(endorser)
}
if retry {
endorser = plan.nextPeerInGroup(endorser)
} else {
done <- newRpcError(code, "evaluate call to endorser returned error: "+message, errDetails...)
}
if endorser == nil {
done <- newRpcError(code, "failed to evaluate transaction, see attached details for more info", errDetails...)
}
}
}()
select {
case status := <-done:
if status != nil {
return nil, status
}
case <-ctx.Done():
// Overall evaluation timeout expired
logger.Warnw("Evaluate call timed out while processing request", "channel", request.GetChannelId(), "txID", request.GetTransactionId())
return nil, newRpcError(codes.DeadlineExceeded, "evaluate timeout expired")
}
}
evaluateResponse := &gp.EvaluateResponse{
Result: response,
}
logger.Debugw("Evaluate call to endorser returned success", "channel", request.GetChannelId(), "txID", request.GetTransactionId(), "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "status", response.GetStatus(), "message", response.GetMessage())
return evaluateResponse, nil
}
func getChannelAndChaincodeFromSignedProposal(signedProposal *peer.SignedProposal) (string, string, bool, error) {
if len(signedProposal.GetProposalBytes()) == 0 {
return "", "", false, fmt.Errorf("a signed proposal is required")
}
proposal, err := protoutil.UnmarshalProposal(signedProposal.GetProposalBytes())
if err != nil {
return "", "", false, err
}
header, err := protoutil.UnmarshalHeader(proposal.GetHeader())
if err != nil {
return "", "", false, err
}
channelHeader, err := protoutil.UnmarshalChannelHeader(header.GetChannelHeader())
if err != nil {
return "", "", false, err
}
payload, err := protoutil.UnmarshalChaincodeProposalPayload(proposal.GetPayload())
if err != nil {
return "", "", false, err
}
spec, err := protoutil.UnmarshalChaincodeInvocationSpec(payload.GetInput())
if err != nil {
return "", "", false, err
}
if len(channelHeader.GetChannelId()) == 0 {
return "", "", false, fmt.Errorf("no channel id provided")
}
if spec.GetChaincodeSpec() == nil {
return "", "", false, fmt.Errorf("no chaincode spec is provided, channel id [%s]", channelHeader.GetChannelId())
}
if spec.GetChaincodeSpec().GetChaincodeId() == nil {
return "", "", false, fmt.Errorf("no chaincode id is provided, channel id [%s]", channelHeader.GetChannelId())
}
if len(spec.GetChaincodeSpec().GetChaincodeId().GetName()) == 0 {
return "", "", false, fmt.Errorf("no chaincode name is provided, channel id [%s]", channelHeader.GetChannelId())
}
return channelHeader.GetChannelId(), spec.GetChaincodeSpec().GetChaincodeId().GetName(), len(payload.TransientMap) > 0, nil
}
func getResultFromProposalResponse(proposalResponse *peer.ProposalResponse) ([]byte, error) {
responsePayload := &peer.ProposalResponsePayload{}
if err := proto.Unmarshal(proposalResponse.GetPayload(), responsePayload); err != nil {
return nil, errors.Wrap(err, "failed to deserialize proposal response payload")
}
return getResultFromProposalResponsePayload(responsePayload)
}
func getResultFromProposalResponsePayload(responsePayload *peer.ProposalResponsePayload) ([]byte, error) {
chaincodeAction := &peer.ChaincodeAction{}
if err := proto.Unmarshal(responsePayload.GetExtension(), chaincodeAction); err != nil {
return nil, errors.Wrap(err, "failed to deserialize chaincode action")
}
return chaincodeAction.GetResponse().GetPayload(), nil
}