Skip to content

Commit

Permalink
Better error messages from Gateway
Browse files Browse the repository at this point in the history
Include some of the detail information in the top level gRPC error message to aid problem determination for client applications.

Signed-off-by: Mark S. Lewis <mark_lewis@uk.ibm.com>
  • Loading branch information
bestbeforetoday authored and denyeart committed Aug 11, 2021
1 parent 497a177 commit 474badd
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 64 deletions.
56 changes: 18 additions & 38 deletions internal/pkg/gateway/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,13 @@ func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*g
response, err := endorser.client.ProcessProposal(ctx, signedProposal)
if err != nil {
logger.Debugw("Evaluate call to endorser failed", "channel", request.ChannelId, "txid", request.TransactionId, "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "error", err)
return nil, rpcError(
codes.Aborted,
"failed to evaluate transaction",
&gp.EndpointError{Address: endorser.address, MspId: endorser.mspid, Message: err.Error()},
)
return nil, wrappedRpcError(err, "failed to evaluate transaction", endpointError(endorser.endpointConfig, err))
}

retVal, err := getTransactionResponse(response)
if err != nil {
logger.Debugw("Evaluate call to endorser returned failure", "channel", request.ChannelId, "txid", request.TransactionId, "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "error", err)
return nil, rpcError(
codes.Aborted,
"transaction evaluation error",
&gp.EndpointError{Address: endorser.address, MspId: endorser.mspid, Message: err.Error()},
)
logger.Debugw("Evaluate call to endorser returned a malformed or error response", "channel", request.ChannelId, "txid", request.TransactionId, "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "error", err)
return nil, rpcError(codes.Unknown, err.Error(), endpointError(endorser.endpointConfig, err))
}
evaluateResponse := &gp.EvaluateResponse{
Result: retVal,
Expand Down Expand Up @@ -144,10 +136,13 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp.
// 2. Process the proposal on this endorser
firstResponse, err := firstEndorser.client.ProcessProposal(ctx, signedProposal)
if err != nil {
return nil, rpcError(codes.Aborted, "failed to endorse transaction", endpointError(firstEndorser, err))
return nil, wrappedRpcError(err, "failed to endorse transaction", endpointError(firstEndorser.endpointConfig, err))
}
if firstResponse.Response.Status < 200 || firstResponse.Response.Status >= 400 {
return nil, rpcError(codes.Aborted, "failed to endorse transaction", endpointError(firstEndorser, fmt.Errorf("error %d, %s", firstResponse.Response.Status, firstResponse.Response.Message)))
err := fmt.Errorf("error %d, %s", firstResponse.Response.Status, firstResponse.Response.Message)
endpointErr := endpointError(firstEndorser.endpointConfig, err)
errorMessage := "failed to endorse transaction: " + detailsAsString(endpointErr)
return nil, rpcError(codes.Aborted, errorMessage, endpointErr)
}

// 3. Extract ChaincodeInterest and SBE policies
Expand Down Expand Up @@ -194,11 +189,11 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp.
switch {
case err != nil:
logger.Debugw("Endorse call to endorser failed", "channel", request.ChannelId, "txid", request.TransactionId, "numEndorsers", len(endorsers), "endorserAddress", e.endpointConfig.address, "endorserMspid", e.endpointConfig.mspid, "error", err)
responseCh <- &endorserResponse{err: endpointError(e, err)}
responseCh <- &endorserResponse{err: endpointError(e.endpointConfig, err)}
case response.Response.Status < 200 || response.Response.Status >= 400:
// this is an error case and will be returned in the error details to the client
logger.Debugw("Endorse call to endorser returned failure", "channel", request.ChannelId, "txid", request.TransactionId, "numEndorsers", len(endorsers), "endorserAddress", e.endpointConfig.address, "endorserMspid", e.endpointConfig.mspid, "status", response.Response.Status, "message", response.Response.Message)
responseCh <- &endorserResponse{err: endpointError(e, fmt.Errorf("error %d, %s", response.Response.Status, response.Response.Message))}
responseCh <- &endorserResponse{err: endpointError(e.endpointConfig, fmt.Errorf("error %d, %s", response.Response.Status, response.Response.Message))}
default:
logger.Debugw("Endorse call to endorser returned success", "channel", request.ChannelId, "txid", request.TransactionId, "numEndorsers", len(endorsers), "endorserAddress", e.endpointConfig.address, "endorserMspid", e.endpointConfig.mspid, "status", response.Response.Status, "message", response.Response.Message)
responseCh <- &endorserResponse{pr: response}
Expand All @@ -218,7 +213,8 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp.
}

if len(errorDetails) != 0 {
return nil, rpcError(codes.Aborted, "failed to endorse transaction", errorDetails...)
errorMessage := "failed to endorse transaction: " + detailsAsString(errorDetails...)
return nil, rpcError(codes.Aborted, errorMessage, errorDetails...)
}

env, err := protoutil.CreateTx(proposal, responses...)
Expand Down Expand Up @@ -258,35 +254,23 @@ func (gs *Server) Submit(ctx context.Context, request *gp.SubmitRequest) (*gp.Su
}

if len(orderers) == 0 {
return nil, status.Errorf(codes.NotFound, "no broadcastClients discovered")
return nil, status.Errorf(codes.Unavailable, "no broadcastClients discovered")
}

orderer := orderers[0] // send to first orderer for now

broadcast, err := orderer.client.Broadcast(ctx)
if err != nil {
return nil, rpcError(
codes.Aborted,
"failed to create BroadcastClient",
&gp.EndpointError{Address: orderer.address, MspId: orderer.mspid, Message: err.Error()},
)
return nil, wrappedRpcError(err, "failed to create BroadcastClient", endpointError(orderer.endpointConfig, err))
}
logger.Info("Submitting txn to orderer")
if err := broadcast.Send(txn); err != nil {
return nil, rpcError(
codes.Aborted,
"failed to send transaction to orderer",
&gp.EndpointError{Address: orderer.address, MspId: orderer.mspid, Message: err.Error()},
)
return nil, wrappedRpcError(err, "failed to send transaction to orderer", endpointError(orderer.endpointConfig, err))
}

response, err := broadcast.Recv()
if err != nil {
return nil, rpcError(
codes.Aborted,
"failed to receive response from orderer",
&gp.EndpointError{Address: orderer.address, MspId: orderer.mspid, Message: err.Error()},
)
return nil, wrappedRpcError(err, "failed to receive response from orderer", endpointError(orderer.endpointConfig, err))
}

if response == nil {
Expand All @@ -313,7 +297,7 @@ func (gs *Server) CommitStatus(ctx context.Context, signedRequest *gp.SignedComm

request := &gp.CommitStatusRequest{}
if err := proto.Unmarshal(signedRequest.Request, request); err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid status request")
return nil, status.Errorf(codes.InvalidArgument, "invalid status request: %v", err)
}

signedData := &protoutil.SignedData{
Expand Down Expand Up @@ -349,7 +333,7 @@ func (gs *Server) ChaincodeEvents(signedRequest *gp.SignedChaincodeEventsRequest

request := &gp.ChaincodeEventsRequest{}
if err := proto.Unmarshal(signedRequest.Request, request); err != nil {
return status.Error(codes.InvalidArgument, "invalid chaincode events request")
return status.Errorf(codes.InvalidArgument, "invalid chaincode events request: %v", err)
}

signedData := &protoutil.SignedData{
Expand Down Expand Up @@ -379,7 +363,3 @@ func (gs *Server) ChaincodeEvents(signedRequest *gp.SignedChaincodeEventsRequest
// If stream is still open, this was a server-side error; otherwise client won't see it anyway
return status.Error(codes.Unavailable, "failed to read events")
}

func endpointError(e *endorser, err error) *gp.EndpointError {
return &gp.EndpointError{Address: e.address, MspId: e.mspid, Message: err.Error()}
}
52 changes: 26 additions & 26 deletions internal/pkg/gateway/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func TestEvaluate(t *testing.T) {
name: "no endorsers",
plan: endorsementPlan{},
members: []networkMember{},
errString: "no endorsing peers",
errString: "rpc error: code = Unavailable desc = no endorsing peers found for channel: test_channel",
},
{
name: "five endorsers, prefer local org",
Expand Down Expand Up @@ -241,7 +241,7 @@ func TestEvaluate(t *testing.T) {
endpointDefinition: &endpointDef{
proposalError: status.Error(codes.Aborted, "wibble"),
},
errString: "rpc error: code = Aborted desc = failed to evaluate transaction",
errString: "rpc error: code = Aborted desc = failed to evaluate transaction: wibble",
errDetails: []*pb.EndpointError{{
Address: "localhost:7051",
MspId: "msp1",
Expand All @@ -257,7 +257,7 @@ func TestEvaluate(t *testing.T) {
proposalResponseStatus: 400,
proposalResponseMessage: "Mock chaincode error",
},
errString: "rpc error: code = Aborted desc = transaction evaluation error",
errString: "rpc error: code = Unknown desc = error 400, Mock chaincode error",
errDetails: []*pb.EndpointError{{
Address: "peer1:8051",
MspId: "msp1",
Expand All @@ -277,7 +277,7 @@ func TestEvaluate(t *testing.T) {
return nil, nil
})
},
errString: "failed to create new connection: endorser not answering",
errString: "rpc error: code = Unavailable desc = failed to create new connection: endorser not answering",
},
{
name: "dialing orderer endpoint fails",
Expand All @@ -292,7 +292,7 @@ func TestEvaluate(t *testing.T) {
return nil, nil
})
},
errString: "failed to create new connection: orderer not answering",
errString: "rpc error: code = Unavailable desc = failed to create new connection: orderer not answering",
},
{
name: "discovery returns incomplete information - no Properties",
Expand All @@ -302,7 +302,7 @@ func TestEvaluate(t *testing.T) {
PKIid: []byte("ill-defined"),
}})
},
errString: "no endorsing peers found for channel",
errString: "rpc error: code = Unavailable desc = no endorsing peers found for channel: test_channel",
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -378,12 +378,12 @@ func TestEndorse(t *testing.T) {
{
name: "endorse with specified orgs, but fails to satisfy one org",
endorsingOrgs: []string{"msp2", "msp4"},
errString: "failed to find any endorsing peers for org(s): msp4",
errString: "rpc error: code = Unavailable desc = failed to find any endorsing peers for org(s): msp4",
},
{
name: "endorse with specified orgs, but fails to satisfy two orgs",
endorsingOrgs: []string{"msp2", "msp4", "msp5"},
errString: "failed to find any endorsing peers for org(s): msp4, msp5",
errString: "rpc error: code = Unavailable desc = failed to find any endorsing peers for org(s): msp4, msp5",
},
{
name: "endorse with multiple layouts - default choice first layout",
Expand Down Expand Up @@ -496,7 +496,7 @@ func TestEndorse(t *testing.T) {
{"g1": 1, "g3": 1},
{"g2": 1, "g3": 1},
},
errString: "failed to select a set of endorsers that satisfy the endorsement policy",
errString: "rpc error: code = Unavailable desc = failed to select a set of endorsers that satisfy the endorsement policy",
},
{
name: "non-matching responses",
Expand All @@ -505,7 +505,7 @@ func TestEndorse(t *testing.T) {
"g2": {{endorser: peer2Mock, height: 5}}, // msp2
},
localResponse: "different_response",
errString: "failed to assemble transaction: ProposalResponsePayloads do not match",
errString: "rpc error: code = Aborted desc = failed to assemble transaction: ProposalResponsePayloads do not match",
},
{
name: "discovery fails",
Expand All @@ -515,7 +515,7 @@ func TestEndorse(t *testing.T) {
postSetup: func(t *testing.T, def *preparedTest) {
def.discovery.PeersForEndorsementReturns(nil, fmt.Errorf("peach-melba"))
},
errString: "peach-melba",
errString: "rpc error: code = Unavailable desc = discovery service failed to build endorsement plan: peach-melba",
},
{
name: "discovery returns incomplete protos - nil layout",
Expand All @@ -529,7 +529,7 @@ func TestEndorse(t *testing.T) {
}
def.discovery.PeersForEndorsementReturns(ed, nil)
},
errString: "failed to select a set of endorsers that satisfy the endorsement policy",
errString: "rpc error: code = Unavailable desc = failed to select a set of endorsers that satisfy the endorsement policy",
},
{
name: "discovery returns incomplete protos - nil state info",
Expand All @@ -544,7 +544,7 @@ func TestEndorse(t *testing.T) {
}
def.discovery.PeersForEndorsementReturns(ed, nil)
},
errString: "failed to select a set of endorsers that satisfy the endorsement policy",
errString: "rpc error: code = Unavailable desc = failed to select a set of endorsers that satisfy the endorsement policy",
},
{
name: "process proposal fails",
Expand All @@ -554,7 +554,7 @@ func TestEndorse(t *testing.T) {
endpointDefinition: &endpointDef{
proposalError: status.Error(codes.Aborted, "wibble"),
},
errString: "failed to endorse transaction",
errString: "rpc error: code = Aborted desc = failed to endorse transaction: wibble",
errDetails: []*pb.EndpointError{{
Address: "localhost:7051",
MspId: "msp1",
Expand All @@ -573,7 +573,7 @@ func TestEndorse(t *testing.T) {
postSetup: func(t *testing.T, def *preparedTest) {
def.localEndorser.ProcessProposalReturns(createProposalResponse(t, localhostMock.address, "all_good", 200, ""), nil)
},
errString: "failed to endorse transaction",
errString: "rpc error: code = Aborted desc = failed to endorse transaction: [{address:\"peer4:11051\" msp_id:\"msp3\" message:\"rpc error: code = Aborted desc = remote-wobble\" }]",
errDetails: []*pb.EndpointError{{
Address: "peer4:11051",
MspId: "msp3",
Expand All @@ -589,7 +589,7 @@ func TestEndorse(t *testing.T) {
proposalResponseStatus: 400,
proposalResponseMessage: "Mock chaincode error",
},
errString: "rpc error: code = Aborted desc = failed to endorse transaction",
errString: "rpc error: code = Aborted desc = failed to endorse transaction: [{address:\"localhost:7051\" msp_id:\"msp1\" message:\"error 400, Mock chaincode error\" }]",
errDetails: []*pb.EndpointError{{
Address: "localhost:7051",
MspId: "msp1",
Expand All @@ -609,7 +609,7 @@ func TestEndorse(t *testing.T) {
postSetup: func(t *testing.T, def *preparedTest) {
def.localEndorser.ProcessProposalReturns(createProposalResponse(t, localhostMock.address, "all_good", 200, ""), nil)
},
errString: "failed to endorse transaction",
errString: "rpc error: code = Aborted desc = failed to endorse transaction: [{address:\"peer4:11051\" msp_id:\"msp3\" message:\"error 400, Mock chaincode error\" }]",
errDetails: []*pb.EndpointError{{
Address: "peer4:11051",
MspId: "msp3",
Expand Down Expand Up @@ -675,7 +675,7 @@ func TestSubmit(t *testing.T) {
postSetup: func(t *testing.T, def *preparedTest) {
def.discovery.ConfigReturnsOnCall(1, nil, fmt.Errorf("jabberwocky"))
},
errString: "jabberwocky",
errString: "rpc error: code = Unavailable desc = jabberwocky",
},
{
name: "no orderers",
Expand All @@ -688,7 +688,7 @@ func TestSubmit(t *testing.T) {
Msps: map[string]*msp.FabricMSPConfig{},
}, nil)
},
errString: "no broadcastClients discovered",
errString: "rpc error: code = Unavailable desc = no broadcastClients discovered",
},
{
name: "orderer broadcast fails",
Expand All @@ -699,7 +699,7 @@ func TestSubmit(t *testing.T) {
proposalResponseStatus: 200,
ordererBroadcastError: status.Error(codes.FailedPrecondition, "Orderer not listening!"),
},
errString: "rpc error: code = Aborted desc = failed to create BroadcastClient",
errString: "rpc error: code = FailedPrecondition desc = failed to create BroadcastClient: Orderer not listening!",
errDetails: []*pb.EndpointError{{
Address: "orderer:7050",
MspId: "msp1",
Expand All @@ -715,7 +715,7 @@ func TestSubmit(t *testing.T) {
proposalResponseStatus: 200,
ordererSendError: status.Error(codes.Internal, "Orderer says no!"),
},
errString: "rpc error: code = Aborted desc = failed to send transaction to orderer",
errString: "rpc error: code = Internal desc = failed to send transaction to orderer: Orderer says no!",
errDetails: []*pb.EndpointError{{
Address: "orderer:7050",
MspId: "msp1",
Expand All @@ -731,7 +731,7 @@ func TestSubmit(t *testing.T) {
proposalResponseStatus: 200,
ordererRecvError: status.Error(codes.FailedPrecondition, "Orderer not happy!"),
},
errString: "rpc error: code = Aborted desc = failed to receive response from orderer",
errString: "rpc error: code = FailedPrecondition desc = failed to receive response from orderer: Orderer not happy!",
errDetails: []*pb.EndpointError{{
Address: "orderer:7050",
MspId: "msp1",
Expand All @@ -752,7 +752,7 @@ func TestSubmit(t *testing.T) {
return abc
}
},
errString: "received nil response from orderer",
errString: "rpc error: code = Aborted desc = received nil response from orderer",
},
{
name: "orderer returns unsuccessful response",
Expand All @@ -771,7 +771,7 @@ func TestSubmit(t *testing.T) {
return abc
}
},
errString: cp.Status_name[int32(cp.Status_BAD_REQUEST)],
errString: "rpc error: code = Aborted desc = received unsuccessful response from orderer: " + cp.Status_name[int32(cp.Status_BAD_REQUEST)],
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -998,7 +998,7 @@ func TestChaincodeEvents(t *testing.T) {
},
},
},
errString: "SEND_ERROR",
errString: "rpc error: code = Aborted desc = SEND_ERROR",
postSetup: func(t *testing.T, test *preparedTest) {
test.eventsServer.SendReturns(status.Error(codes.Aborted, "SEND_ERROR"))
},
Expand Down Expand Up @@ -1208,7 +1208,7 @@ func prepareTest(t *testing.T, tt *testDef) *preparedTest {
}

func checkError(t *testing.T, err error, errString string, details []*pb.EndpointError) {
require.ErrorContains(t, err, errString)
require.EqualError(t, err, errString)
s, ok := status.FromError(err)
require.True(t, ok, "Expected a gRPC status error")
require.Len(t, s.Details(), len(details))
Expand Down
19 changes: 19 additions & 0 deletions internal/pkg/gateway/apiutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"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"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -79,3 +80,21 @@ func rpcError(code codes.Code, message string, details ...proto.Message) error {
}
return st.Err()
}

func wrappedRpcError(err error, message string, details ...proto.Message) error {
statusErr := status.Convert(err)
return rpcError(statusErr.Code(), message+": "+statusErr.Message(), details...)
}

func endpointError(e *endpointConfig, err error) *gp.EndpointError {
return &gp.EndpointError{Address: e.address, MspId: e.mspid, Message: err.Error()}
}

func detailsAsString(details ...proto.Message) string {
var detailStrings []string
for _, detail := range details {
detailStrings = append(detailStrings, "{"+detail.String()+"}")
}

return fmt.Sprint(detailStrings)
}

0 comments on commit 474badd

Please sign in to comment.