From 5113aa910b9c8aca8a8162d56d657d7d3a7238c0 Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Thu, 4 Nov 2021 18:07:46 +0000 Subject: [PATCH] Better gRPC error on context error from CommitStatus service Rather than always return a FailedPrecondition error on failure obtaining commit status, return either a DeadlineExceeded or Canceled error on a context error. This may happen if the call is cancelled from the client end, either by an explicit context cancel or timeout. Signed-off-by: Mark S. Lewis --- internal/pkg/gateway/api.go | 12 ++++++------ internal/pkg/gateway/api_test.go | 13 +++++++++++-- internal/pkg/gateway/apiutils.go | 18 ++++++++++++++++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/internal/pkg/gateway/api.go b/internal/pkg/gateway/api.go index a0fc84959e1..372ffe9cc84 100644 --- a/internal/pkg/gateway/api.go +++ b/internal/pkg/gateway/api.go @@ -77,7 +77,7 @@ func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*g gs.registry.removeEndorser(endorser) endorser = plan.retry(endorser) if endorser == nil { - return nil, rpcError(codes.Aborted, "failed to evaluate transaction, see attached details for more info", errDetails...) + return nil, newRpcError(codes.Aborted, "failed to evaluate transaction, see attached details for more info", errDetails...) } } @@ -88,7 +88,7 @@ func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*g endpointErr := errorDetail(endorser.endpointConfig, err) errDetails = append(errDetails, endpointErr) // this is a chaincode error response - don't retry - return nil, rpcError(codes.Aborted, "evaluate call to endorser returned error: "+response.Message, errDetails...) + return nil, newRpcError(codes.Aborted, "evaluate call to endorser returned error: "+response.Message, errDetails...) } } @@ -176,7 +176,7 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp. } } if firstEndorser == nil || firstResponse == nil { - return nil, rpcError(codes.Aborted, "failed to endorse transaction, see attached details for more info", errDetails...) + return nil, newRpcError(codes.Aborted, "failed to endorse transaction, see attached details for more info", errDetails...) } // 3. Extract ChaincodeInterest and SBE policies @@ -258,7 +258,7 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp. } if endorsements == nil { - return nil, rpcError(codes.Aborted, "failed to collect enough transaction endorsements, see attached details for more info", errorDetails...) + return nil, newRpcError(codes.Aborted, "failed to collect enough transaction endorsements, see attached details for more info", errorDetails...) } env, err := protoutil.CreateTx(proposal, endorsements...) @@ -309,7 +309,7 @@ func (gs *Server) Submit(ctx context.Context, request *gp.SubmitRequest) (*gp.Su errDetails = append(errDetails, errorDetail(orderer.endpointConfig, err)) } - return nil, rpcError(codes.Aborted, "no orderers could successfully process transaction", errDetails...) + return nil, newRpcError(codes.Aborted, "no orderers could successfully process transaction", errDetails...) } func (gs *Server) broadcast(ctx context.Context, orderer *orderer, txn *common.Envelope) error { @@ -363,7 +363,7 @@ func (gs *Server) CommitStatus(ctx context.Context, signedRequest *gp.SignedComm txStatus, err := gs.commitFinder.TransactionStatus(ctx, request.ChannelId, request.TransactionId) if err != nil { - return nil, status.Error(codes.FailedPrecondition, err.Error()) + return nil, toRpcError(err, codes.FailedPrecondition) } response := &gp.CommitStatusResponse{ diff --git a/internal/pkg/gateway/api_test.go b/internal/pkg/gateway/api_test.go index 5050b951e02..6a03cbda8ee 100644 --- a/internal/pkg/gateway/api_test.go +++ b/internal/pkg/gateway/api_test.go @@ -1198,6 +1198,16 @@ func TestCommitStatus(t *testing.T) { BlockNumber: 101, }, }, + { + name: "context timeout", + finderErr: context.DeadlineExceeded, + errString: "rpc error: code = DeadlineExceeded desc = context deadline exceeded", + }, + { + name: "context canceled", + finderErr: context.Canceled, + errString: "rpc error: code = Canceled desc = context canceled", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1624,7 +1634,7 @@ func TestNilArgs(t *testing.T) { } func TestRpcErrorWithBadDetails(t *testing.T) { - err := rpcError(codes.InvalidArgument, "terrible error", nil) + err := newRpcError(codes.InvalidArgument, "terrible error", nil) require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "terrible error")) } @@ -1741,7 +1751,6 @@ func prepareTest(t *testing.T, tt *testDef) *preparedTest { dialer.Returns(nil, nil) server.registry.endpointFactory = createEndpointFactory(t, epDef, dialer.Spy) - require.NoError(t, err, "Failed to sign the proposal") ctx := context.WithValue(context.Background(), contextKey("orange"), "apples") pt := &preparedTest{ diff --git a/internal/pkg/gateway/apiutils.go b/internal/pkg/gateway/apiutils.go index 736010c0322..7ebe7e2da12 100644 --- a/internal/pkg/gateway/apiutils.go +++ b/internal/pkg/gateway/apiutils.go @@ -45,7 +45,7 @@ func getChannelAndChaincodeFromSignedProposal(signedProposal *peer.SignedProposa return channelHeader.ChannelId, spec.ChaincodeSpec.ChaincodeId.Name, len(payload.TransientMap) > 0, nil } -func rpcError(code codes.Code, message string, details ...proto.Message) error { +func newRpcError(code codes.Code, message string, details ...proto.Message) error { st := status.New(code, message) if len(details) != 0 { std, err := st.WithDetails(details...) @@ -58,7 +58,21 @@ func rpcError(code codes.Code, message string, details ...proto.Message) error { func wrappedRpcError(err error, message string, details ...proto.Message) error { statusErr := status.Convert(err) - return rpcError(statusErr.Code(), message+": "+statusErr.Message(), details...) + return newRpcError(statusErr.Code(), message+": "+statusErr.Message(), details...) +} + +func toRpcError(err error, unknownCode codes.Code) error { + errStatus, ok := status.FromError(err) + if ok { + return errStatus.Err() + } + + errStatus = status.FromContextError(err) + if errStatus.Code() != codes.Unknown { + return errStatus.Err() + } + + return status.Error(unknownCode, err.Error()) } func errorDetail(e *endpointConfig, err error) *gp.ErrorDetail {