Skip to content

Commit 5221019

Browse files
Make Go error details more accessible (#986)
- Add Details() method to transaction errors. - Include error details in Error() message. - Add Unwrap() methods to specific transaction errors so the underlying TransactionError can be retrieved without knowing the specific error type, to provide easier access to TransactionID and Details(). - Change Java stack trace to use similar YAML-like format as Go error details. - Update minor version to 1.10. Signed-off-by: Mark S. Lewis <[email protected]>
1 parent d6cf867 commit 5221019

File tree

9 files changed

+261
-43
lines changed

9 files changed

+261
-43
lines changed

.github/workflows/verify-versions.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ permissions:
77
contents: read
88

99
env:
10-
GATEWAY_VERSION: 1.9.1
10+
GATEWAY_VERSION: 1.10.0
1111

1212
jobs:
1313
go:

java/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<groupId>org.hyperledger.fabric</groupId>
99
<artifactId>fabric-gateway</artifactId>
10-
<version>1.9.1-SNAPSHOT</version>
10+
<version>1.10.0-SNAPSHOT</version>
1111
<packaging>jar</packaging>
1212

1313
<name>fabric-gateway</name>

java/src/main/java/org/hyperledger/fabric/client/GrpcStackTracePrinter.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ public void printStackTrace(final PrintWriter out) {
3838

3939
List<ErrorDetail> details = grpcStatus.getDetails();
4040
if (!details.isEmpty()) {
41-
message.append("Error details:\n");
41+
message.append("Error details:");
4242
for (ErrorDetail detail : details) {
43-
message.append(" address: ")
43+
message.append("\n - address: ")
4444
.append(detail.getAddress())
45-
.append("; mspId: ")
45+
.append("\n mspId: ")
4646
.append(detail.getMspId())
47-
.append("; message: ")
48-
.append(detail.getMessage())
49-
.append('\n');
47+
.append("\n message: ")
48+
.append(detail.getMessage());
5049
}
50+
message.append('\n');
5151
}
5252

5353
out.print(message);

node/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hyperledger/fabric-gateway",
3-
"version": "1.9.1",
3+
"version": "1.10.0",
44
"description": "Hyperledger Fabric Gateway client API for Node",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

pkg/client/errors.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package client
55

66
import (
77
"fmt"
8+
"strings"
89

10+
"github.com/hyperledger/fabric-protos-go-apiv2/gateway"
911
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
1012
"google.golang.org/grpc/status"
1113
)
@@ -28,32 +30,98 @@ func newTransactionError(err error, transactionID string) *TransactionError {
2830
}
2931

3032
return &TransactionError{
31-
grpcError: &grpcError{err},
33+
grpcError: grpcError{err},
3234
TransactionID: transactionID,
3335
}
3436
}
3537

38+
func (e *grpcError) Details() []*gateway.ErrorDetail {
39+
var results []*gateway.ErrorDetail
40+
41+
for _, detail := range e.GRPCStatus().Details() {
42+
switch detail := detail.(type) {
43+
case *gateway.ErrorDetail:
44+
results = append(results, detail)
45+
}
46+
}
47+
48+
return results
49+
}
50+
3651
// TransactionError represents an error invoking a transaction. This is a gRPC [status] error.
3752
type TransactionError struct {
38-
*grpcError
53+
grpcError
3954
TransactionID string
4055
}
4156

57+
// Error message including attached details.
58+
func (e *TransactionError) Error() string {
59+
var result strings.Builder
60+
result.WriteString(e.grpcError.Error())
61+
62+
details := e.Details()
63+
if len(details) == 0 {
64+
return result.String()
65+
}
66+
67+
result.WriteString("\nDetails:")
68+
for _, detail := range details {
69+
result.WriteString("\n - Address: ")
70+
result.WriteString(detail.GetAddress())
71+
result.WriteString("\n MspId: ")
72+
result.WriteString(detail.GetMspId())
73+
result.WriteString("\n Message: ")
74+
result.WriteString(detail.GetMessage())
75+
}
76+
77+
return result.String()
78+
}
79+
4280
// EndorseError represents a failure endorsing a transaction proposal.
4381
type EndorseError struct {
4482
*TransactionError
4583
}
4684

85+
// Error message including attached details.
86+
func (e *EndorseError) Error() string {
87+
return fmt.Sprintf("endorse error: %s", e.TransactionError)
88+
}
89+
90+
// Unwrap the next error in the error chain
91+
func (e *EndorseError) Unwrap() error {
92+
return e.TransactionError
93+
}
94+
4795
// SubmitError represents a failure submitting an endorsed transaction to the orderer.
4896
type SubmitError struct {
4997
*TransactionError
5098
}
5199

100+
// Error message including attached details.
101+
func (e *SubmitError) Error() string {
102+
return fmt.Sprintf("submit error: %s", e.TransactionError)
103+
}
104+
105+
// Unwrap the next error in the error chain
106+
func (e *SubmitError) Unwrap() error {
107+
return e.TransactionError
108+
}
109+
52110
// CommitStatusError represents a failure obtaining the commit status of a transaction.
53111
type CommitStatusError struct {
54112
*TransactionError
55113
}
56114

115+
// Error message including attached details.
116+
func (e *CommitStatusError) Error() string {
117+
return fmt.Sprintf("commit status error: %s", e.TransactionError)
118+
}
119+
120+
// Unwrap the next error in the error chain
121+
func (e *CommitStatusError) Unwrap() error {
122+
return e.TransactionError
123+
}
124+
57125
func newCommitError(transactionID string, code peer.TxValidationCode) error {
58126
return &CommitError{
59127
message: fmt.Sprintf("transaction %s failed to commit with status code %d (%s)", transactionID, int32(code), peer.TxValidationCode_name[int32(code)]),

pkg/client/errors_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright IBM Corp. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package client
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hyperledger/fabric-protos-go-apiv2/gateway"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"google.golang.org/grpc/codes"
13+
"google.golang.org/grpc/status"
14+
"google.golang.org/protobuf/protoadapt"
15+
)
16+
17+
func newGrpcStatus(code codes.Code, message string, details ...*gateway.ErrorDetail) (*status.Status, error) {
18+
result := status.New(code, message)
19+
if len(details) == 0 {
20+
return result, nil
21+
}
22+
23+
var v1Details []protoadapt.MessageV1
24+
for _, detail := range details {
25+
v1Details = append(v1Details, protoadapt.MessageV1Of(detail))
26+
}
27+
28+
resultWithDetails, err := result.WithDetails(v1Details...)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
return resultWithDetails, nil
34+
}
35+
36+
func TestErrors(t *testing.T) {
37+
for typeName, newInstance := range map[string](func(*TransactionError) error){
38+
"endorse error": func(txErr *TransactionError) error {
39+
return &EndorseError{txErr}
40+
},
41+
"submit error": func(txErr *TransactionError) error {
42+
return &SubmitError{txErr}
43+
},
44+
"commit status error": func(txErr *TransactionError) error {
45+
return &CommitStatusError{txErr}
46+
},
47+
} {
48+
t.Run(typeName+" with details", func(t *testing.T) {
49+
details := []*gateway.ErrorDetail{
50+
{
51+
Address: "ADDRESS_1",
52+
MspId: "MSP_ID_1",
53+
Message: "MESSAGE_1",
54+
},
55+
{
56+
Address: "ADDRESS_2",
57+
MspId: "MSP_ID_2",
58+
Message: "MESSAGE_2",
59+
},
60+
}
61+
grpcStatus, err := newGrpcStatus(codes.Aborted, "STATUS_MESSAGE", details...)
62+
require.NoError(t, err)
63+
64+
transactionErr := newTransactionError(grpcStatus.Err(), "TRANSACTION_ID")
65+
actualErr := newInstance(transactionErr)
66+
67+
t.Run("Error", func(t *testing.T) {
68+
require.ErrorContains(t, actualErr, grpcStatus.Err().Error())
69+
require.ErrorContains(t, actualErr, typeName)
70+
for _, detail := range details {
71+
require.ErrorContains(t, actualErr, detail.GetAddress())
72+
require.ErrorContains(t, actualErr, detail.GetMspId())
73+
require.ErrorContains(t, actualErr, detail.GetMessage())
74+
}
75+
})
76+
77+
t.Run("Details", func(t *testing.T) {
78+
expectedErr := new(TransactionError)
79+
require.ErrorAs(t, actualErr, &expectedErr)
80+
81+
require.Len(t, expectedErr.Details(), len(details), "number of detail messages")
82+
for i, actual := range expectedErr.Details() {
83+
expected := details[i]
84+
AssertProtoEqual(t, expected, actual)
85+
}
86+
})
87+
})
88+
89+
t.Run(typeName+" without details", func(t *testing.T) {
90+
grpcStatus, err := newGrpcStatus(codes.Aborted, "STATUS_MESSAGE")
91+
require.NoError(t, err)
92+
93+
transactionErr := newTransactionError(grpcStatus.Err(), "TRANSACTION_ID")
94+
actualErr := newInstance(transactionErr)
95+
96+
t.Run("Error", func(t *testing.T) {
97+
require.ErrorContains(t, actualErr, grpcStatus.Err().Error())
98+
require.ErrorContains(t, actualErr, typeName)
99+
})
100+
101+
t.Run("TransactionID", func(t *testing.T) {
102+
var actualTransactionErr *TransactionError
103+
require.ErrorAs(t, actualErr, &actualTransactionErr)
104+
105+
require.Equal(t, transactionErr.TransactionID, actualTransactionErr.TransactionID)
106+
})
107+
108+
t.Run("Details", func(t *testing.T) {
109+
var actualTransactionErr *TransactionError
110+
require.ErrorAs(t, actualErr, &actualTransactionErr)
111+
112+
require.Empty(t, actualTransactionErr.Details())
113+
})
114+
})
115+
}
116+
117+
t.Run("Consistent behavior", func(t *testing.T) {
118+
grpcStatus, err := newGrpcStatus(codes.Aborted, "STATUS_MESSAGE")
119+
require.NoError(t, err)
120+
121+
transactionErr := newTransactionError(grpcStatus.Err(), "TRANSACTION_ID")
122+
123+
t.Run("EndorseError", func(t *testing.T) {
124+
actualErr := &EndorseError{transactionErr}
125+
126+
var endorseErr *EndorseError
127+
require.ErrorAs(t, actualErr, &endorseErr)
128+
129+
assert.Equal(t, transactionErr.TransactionID, endorseErr.TransactionID)
130+
assert.Empty(t, endorseErr.Details())
131+
})
132+
133+
t.Run("SubmitError", func(t *testing.T) {
134+
actualErr := &SubmitError{transactionErr}
135+
136+
var submitErr *SubmitError
137+
require.ErrorAs(t, actualErr, &submitErr)
138+
139+
assert.Equal(t, transactionErr.TransactionID, submitErr.TransactionID)
140+
assert.Empty(t, submitErr.Details())
141+
})
142+
143+
t.Run("CommitStatusError", func(t *testing.T) {
144+
actualErr := &CommitStatusError{transactionErr}
145+
146+
var commitStatusErr *CommitStatusError
147+
require.ErrorAs(t, actualErr, &commitStatusErr)
148+
149+
assert.Equal(t, transactionErr.TransactionID, commitStatusErr.TransactionID)
150+
assert.Empty(t, commitStatusErr.Details())
151+
})
152+
})
153+
}

0 commit comments

Comments
 (0)