171 lines
6.6 KiB
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
|
|
}
|