Skip to content

Commit 5e49366

Browse files
committed
[FAB-3560] Endorsement failure doesn't return response
This CR ensures that the response is returned when endorsement fails due to the chaincode returning an ERROR status (currently, status>=500). The details in the response, including the status code, message, and payload from the chaincode are vital pieces of information for understanding the endorsement failure and acting on it, if necessary. This CR also adds logging messages that were helpful in debugging the issue and cleans up a few other logging messages. Change-Id: I7189c1c5c32e0dd6cd128ec122eefa1841c74ab3 Signed-off-by: Will Lahti <[email protected]>
1 parent 3b40efa commit 5e49366

File tree

6 files changed

+212
-16
lines changed

6 files changed

+212
-16
lines changed

core/endorser/endorser.go

+38-8
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,23 @@ func (*Endorser) checkEsccAndVscc(prop *pb.Proposal) error {
8080
func (*Endorser) getTxSimulator(ledgername string) (ledger.TxSimulator, error) {
8181
lgr := peer.GetLedger(ledgername)
8282
if lgr == nil {
83-
return nil, fmt.Errorf("chain does not exist(%s)", ledgername)
83+
return nil, fmt.Errorf("channel does not exist: %s", ledgername)
8484
}
8585
return lgr.NewTxSimulator()
8686
}
8787

8888
func (*Endorser) getHistoryQueryExecutor(ledgername string) (ledger.HistoryQueryExecutor, error) {
8989
lgr := peer.GetLedger(ledgername)
9090
if lgr == nil {
91-
return nil, fmt.Errorf("chain does not exist(%s)", ledgername)
91+
return nil, fmt.Errorf("channel does not exist: %s", ledgername)
9292
}
9393
return lgr.NewHistoryQueryExecutor()
9494
}
9595

9696
//call specified chaincode (system or user)
9797
func (e *Endorser) callChaincode(ctxt context.Context, chainID string, version string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cis *pb.ChaincodeInvocationSpec, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (*pb.Response, *pb.ChaincodeEvent, error) {
98+
endorserLogger.Debugf("Entry - txid: %s channel id: %s version: %s", txid, chainID, version)
99+
defer endorserLogger.Debugf("Exit")
98100
var err error
99101
var res *pb.Response
100102
var ccevent *pb.ChaincodeEvent
@@ -117,9 +119,8 @@ func (e *Endorser) callChaincode(ctxt context.Context, chainID string, version s
117119
//per doc anything < 500 can be sent as TX.
118120
//fabric errors will always be >= 500 (ie, unambiguous errors )
119121
//"lscc" will respond with status 200 or >=500 (ie, unambiguous OK or ERROR)
120-
//This leaves all < 500 errors to user chaincodes
121122
if res.Status >= shim.ERROR {
122-
return nil, nil, fmt.Errorf(string(res.Message))
123+
return res, nil, nil
123124
}
124125

125126
//----- BEGIN - SECTION THAT MAY NEED TO BE DONE IN LSCC ------
@@ -200,6 +201,8 @@ func (e *Endorser) disableJavaCCInst(cid *pb.ChaincodeID, cis *pb.ChaincodeInvoc
200201

201202
//simulate the proposal by calling the chaincode
202203
func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (*ccprovider.ChaincodeData, *pb.Response, []byte, *pb.ChaincodeEvent, error) {
204+
endorserLogger.Debugf("Entry - txid: %s channel id: %s", txid, chainID)
205+
defer endorserLogger.Debugf("Exit")
203206
//we do expect the payload to be a ChaincodeInvocationSpec
204207
//if we are supporting other payloads in future, this be glaringly point
205208
//as something that should change
@@ -263,6 +266,7 @@ func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid st
263266
var ccevent *pb.ChaincodeEvent
264267
res, ccevent, err = e.callChaincode(ctx, chainID, version, txid, signedProp, prop, cis, cid, txsim)
265268
if err != nil {
269+
endorserLogger.Errorf("callChaincode() failed for txid: %s error: %s", txid, err)
266270
return nil, nil, nil, nil, err
267271
}
268272

@@ -286,7 +290,8 @@ func (e *Endorser) getCDSFromLSCC(ctx context.Context, chainID string, txid stri
286290

287291
//endorse the proposal by calling the ESCC
288292
func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, proposal *pb.Proposal, response *pb.Response, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator, cd *ccprovider.ChaincodeData) (*pb.ProposalResponse, error) {
289-
endorserLogger.Debugf("endorseProposal starts for chainID %s, ccid %s", chainID, ccid)
293+
endorserLogger.Debugf("Entry - txid: %s channel id: %s chaincode id: %s", txid, chainID, ccid)
294+
defer endorserLogger.Debugf("Exit")
290295

291296
isSysCC := cd == nil
292297
// 1) extract the name of the escc that is requested to endorse this chaincode
@@ -303,7 +308,7 @@ func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid str
303308
}
304309
}
305310

306-
endorserLogger.Debugf("endorseProposal info: escc for cid %s is %s", ccid, escc)
311+
endorserLogger.Debugf("info: escc for chaincode id %s is %s", ccid, escc)
307312

308313
// marshalling event bytes
309314
var err error
@@ -353,7 +358,7 @@ func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid str
353358
}
354359

355360
if res.Status >= shim.ERROR {
356-
return nil, fmt.Errorf(string(res.Message))
361+
return &pb.ProposalResponse{Response: res}, nil
357362
}
358363

359364
prBytes := res.Payload
@@ -377,6 +382,8 @@ func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid str
377382

378383
// ProcessProposal process the Proposal
379384
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
385+
endorserLogger.Debugf("Entry")
386+
defer endorserLogger.Debugf("Exit")
380387
// at first, we check whether the message is valid
381388
prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp)
382389
if err != nil {
@@ -395,7 +402,7 @@ func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedPro
395402

396403
// block invocations to security-sensitive system chaincodes
397404
if syscc.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) {
398-
endorserLogger.Errorf("ProcessProposal error: an attempt was made by %#v to invoke system chaincode %s",
405+
endorserLogger.Errorf("Error: an attempt was made by %#v to invoke system chaincode %s",
399406
shdr.Creator, hdrExt.ChaincodeId.Name)
400407
err = fmt.Errorf("Chaincode %s cannot be invoked through a proposal", hdrExt.ChaincodeId.Name)
401408
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
@@ -468,6 +475,23 @@ func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedPro
468475
if err != nil {
469476
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
470477
}
478+
if res != nil {
479+
if res.Status >= shim.ERROR {
480+
endorserLogger.Debugf("simulateProposal() resulted in chaincode error for txid: %s", txid)
481+
var cceventBytes []byte
482+
if ccevent != nil {
483+
cceventBytes, err = putils.GetBytesChaincodeEvent(ccevent)
484+
if err != nil {
485+
return nil, fmt.Errorf("failed to marshal event bytes - %s", err)
486+
}
487+
}
488+
pResp, err := putils.CreateProposalResponseFailure(prop.Header, prop.Payload, res, simulationResult, cceventBytes, hdrExt.ChaincodeId, hdrExt.PayloadVisibility)
489+
if err != nil {
490+
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
491+
}
492+
return pResp, nil
493+
}
494+
}
471495

472496
//2 -- endorse and get a marshalled ProposalResponse message
473497
var pResp *pb.ProposalResponse
@@ -481,6 +505,12 @@ func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedPro
481505
if err != nil {
482506
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
483507
}
508+
if pResp != nil {
509+
if res.Status >= shim.ERROR {
510+
endorserLogger.Debugf("endorseProposal() resulted in chaincode error for txid: %s", txid)
511+
return pResp, nil
512+
}
513+
}
484514
}
485515

486516
// Set the proposal response payload - it

core/endorser/endorser_test.go

+17-4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import (
5151
"github.com/spf13/viper"
5252
"golang.org/x/net/context"
5353
"google.golang.org/grpc"
54+
55+
"github.com/stretchr/testify/assert"
5456
"google.golang.org/grpc/credentials"
5557
)
5658

@@ -477,7 +479,21 @@ func TestDeployAndInvoke(t *testing.T) {
477479
return
478480
}
479481

480-
fmt.Printf("Invoke test passed\n")
482+
// Test chaincode endorsement failure when invalid function name supplied
483+
f = "invokeinvalid"
484+
invokeArgs = append([]string{f}, args...)
485+
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}}
486+
prop, resp, txid, nonce, err = invoke(chainID, spec)
487+
if err != nil {
488+
t.Fail()
489+
t.Logf("Error invoking transaction: %s", err)
490+
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
491+
return
492+
}
493+
if resp != nil {
494+
assert.Equal(t, int32(500), resp.Response.Status, "Unexpected response status")
495+
}
496+
481497
t.Logf("Invoke test passed")
482498

483499
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
@@ -535,7 +551,6 @@ func TestDeployAndUpgrade(t *testing.T) {
535551
return
536552
}
537553

538-
fmt.Printf("Upgrade test passed\n")
539554
t.Logf("Upgrade test passed")
540555

541556
chaincode.GetChain().Stop(ctxt, cccid1, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID1}})
@@ -602,7 +617,6 @@ func TestWritersACLFail(t *testing.T) {
602617
return
603618
}
604619

605-
fmt.Println("TestWritersACLFail passed")
606620
t.Logf("TestWritersACLFail passed")
607621

608622
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
@@ -647,7 +661,6 @@ func TestAdminACLFail(t *testing.T) {
647661
return
648662
}
649663

650-
fmt.Println("TestAdminACLFail passed")
651664
t.Logf("TestATestAdminACLFailCLFail passed")
652665

653666
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})

peer/chaincode/common.go

+19-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/hyperledger/fabric/common/cauthdsl"
2727
"github.com/hyperledger/fabric/core/chaincode"
2828
"github.com/hyperledger/fabric/core/chaincode/platforms"
29+
"github.com/hyperledger/fabric/core/chaincode/shim"
2930
"github.com/hyperledger/fabric/core/container"
3031
"github.com/hyperledger/fabric/msp"
3132
"github.com/hyperledger/fabric/peer/common"
@@ -104,11 +105,24 @@ func chaincodeInvokeOrQuery(cmd *cobra.Command, args []string, invoke bool, cf *
104105
proposalResp, err := ChaincodeInvokeOrQuery(spec, chainID, invoke,
105106
cf.Signer, cf.EndorserClient, cf.BroadcastClient)
106107
if err != nil {
107-
return err
108+
return fmt.Errorf("%s - %v", err, proposalResp)
108109
}
109110

110111
if invoke {
111-
logger.Infof("Invoke result: %v", proposalResp)
112+
if proposalResp.Response.Status >= shim.ERROR {
113+
logger.Debugf("ESCC invoke result: %v", proposalResp)
114+
pRespPayload, err := putils.GetProposalResponsePayload(proposalResp.Payload)
115+
if err != nil {
116+
return fmt.Errorf("Error while unmarshaling proposal response payload: %s", err)
117+
}
118+
ca, err := putils.GetChaincodeAction(pRespPayload.Extension)
119+
if err != nil {
120+
return fmt.Errorf("Error while unmarshaling chaincode action: %s", err)
121+
}
122+
logger.Warningf("Endorsement failure during invoke. chaincode result: %v", ca.Response)
123+
} else {
124+
logger.Infof("Invoke result: %v", proposalResp)
125+
}
112126
} else {
113127
if proposalResp == nil {
114128
return fmt.Errorf("Error query %s by endorsing: %s", chainFuncName, err)
@@ -309,6 +323,9 @@ func ChaincodeInvokeOrQuery(spec *pb.ChaincodeSpec, cID string, invoke bool,
309323

310324
if invoke {
311325
if proposalResp != nil {
326+
if proposalResp.Response.Status >= shim.ERROR {
327+
return proposalResp, nil
328+
}
312329
// assemble a signed transaction (it's an Envelope message)
313330
env, err := putils.CreateSignedTx(prop, signer, proposalResp)
314331
if err != nil {

peer/chaincode/invoke_test.go

+78-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@
1717
package chaincode
1818

1919
import (
20+
"bytes"
2021
"errors"
22+
"fmt"
2123
"testing"
2224

25+
"github.com/hyperledger/fabric/common/flogging"
26+
"github.com/hyperledger/fabric/common/util"
2327
"github.com/hyperledger/fabric/msp"
2428
"github.com/hyperledger/fabric/peer/common"
29+
cb "github.com/hyperledger/fabric/protos/common"
2530
pb "github.com/hyperledger/fabric/protos/peer"
31+
"github.com/hyperledger/fabric/protos/utils"
32+
logging "github.com/op/go-logging"
2633
"github.com/stretchr/testify/assert"
2734
)
2835

@@ -116,7 +123,7 @@ func TestInvokeCmd(t *testing.T) {
116123
assert.NoError(t, err)
117124
}
118125

119-
func TestInvokeCmdEndorseFail(t *testing.T) {
126+
func TestInvokeCmdEndorsementError(t *testing.T) {
120127
InitMSP()
121128
mockCF, err := getMockChaincodeCmdFactoryWithErr()
122129
assert.NoError(t, err, "Error getting mock chaincode command factory")
@@ -130,6 +137,35 @@ func TestInvokeCmdEndorseFail(t *testing.T) {
130137
assert.Error(t, err, "Expected error executing invoke command")
131138
}
132139

140+
func TestInvokeCmdEndorsementFailure(t *testing.T) {
141+
InitMSP()
142+
ccRespStatus := int32(502)
143+
ccRespPayload := []byte("Invalid function name")
144+
mockCF, err := getMockChaincodeCmdFactoryEndorsementFailure(ccRespStatus, ccRespPayload)
145+
assert.NoError(t, err, "Error getting mock chaincode command factory")
146+
147+
cmd := invokeCmd(mockCF)
148+
AddFlags(cmd)
149+
args := []string{"-n", "example02", "-p", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02",
150+
"-c", "{\"Args\": [\"invokeinvalid\",\"a\",\"b\",\"10\"]}"}
151+
cmd.SetArgs(args)
152+
153+
// set logger to logger with a backend that writes to a byte buffer
154+
var buffer bytes.Buffer
155+
logger.SetBackend(logging.AddModuleLevel(logging.NewLogBackend(&buffer, "", 0)))
156+
// reset the logger after test
157+
defer func() {
158+
flogging.Reset()
159+
}()
160+
// make sure buffer is "clean" before running the invoke
161+
buffer.Reset()
162+
163+
err = cmd.Execute()
164+
assert.NoError(t, err)
165+
assert.Regexp(t, "Endorsement failure during invoke", buffer.String())
166+
assert.Regexp(t, fmt.Sprintf("chaincode result: status:%d payload:\"%s\"", ccRespStatus, ccRespPayload), buffer.String())
167+
}
168+
133169
// Returns mock chaincode command factory
134170
func getMockChaincodeCmdFactory() (*ChaincodeCmdFactory, error) {
135171
signer, err := common.GetDefaultSigner()
@@ -169,3 +205,44 @@ func getMockChaincodeCmdFactoryWithErr() (*ChaincodeCmdFactory, error) {
169205
}
170206
return mockCF, nil
171207
}
208+
209+
// Returns mock chaincode command factory
210+
func getMockChaincodeCmdFactoryEndorsementFailure(ccRespStatus int32, ccRespPayload []byte) (*ChaincodeCmdFactory, error) {
211+
signer, err := common.GetDefaultSigner()
212+
if err != nil {
213+
return nil, err
214+
}
215+
216+
// create a proposal from a ChaincodeInvocationSpec
217+
prop, _, err := utils.CreateChaincodeProposal(cb.HeaderType_ENDORSER_TRANSACTION, util.GetTestChainID(), createCIS(), nil)
218+
if err != nil {
219+
return nil, fmt.Errorf("Could not create chaincode proposal, err %s\n", err)
220+
}
221+
222+
response := &pb.Response{Status: ccRespStatus, Payload: ccRespPayload}
223+
result := []byte("res")
224+
ccid := &pb.ChaincodeID{Name: "foo", Version: "v1"}
225+
226+
mockRespFailure, err := utils.CreateProposalResponseFailure(prop.Header, prop.Payload, response, result, nil, ccid, nil)
227+
if err != nil {
228+
229+
return nil, fmt.Errorf("Could not create proposal response failure, err %s\n", err)
230+
}
231+
232+
mockEndorserClient := common.GetMockEndorserClient(mockRespFailure, nil)
233+
mockBroadcastClient := common.GetMockBroadcastClient(nil)
234+
mockCF := &ChaincodeCmdFactory{
235+
EndorserClient: mockEndorserClient,
236+
Signer: signer,
237+
BroadcastClient: mockBroadcastClient,
238+
}
239+
return mockCF, nil
240+
}
241+
242+
func createCIS() *pb.ChaincodeInvocationSpec {
243+
return &pb.ChaincodeInvocationSpec{
244+
ChaincodeSpec: &pb.ChaincodeSpec{
245+
Type: pb.ChaincodeSpec_GOLANG,
246+
ChaincodeId: &pb.ChaincodeID{Name: "chaincode_name"},
247+
Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("arg1"), []byte("arg2")}}}}
248+
}

protos/utils/txutils.go

+30-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func CreateProposalResponse(hdrbytes []byte, payl []byte, response *peer.Respons
225225
// get the bytes of the proposal response payload - we need to sign them
226226
prpBytes, err := GetBytesProposalResponsePayload(pHashBytes, response, results, events, ccid)
227227
if err != nil {
228-
return nil, errors.New("Failure while unmarshalling the ProposalResponsePayload")
228+
return nil, errors.New("Failure while marshaling the ProposalResponsePayload")
229229
}
230230

231231
// serialize the signing identity
@@ -250,6 +250,35 @@ func CreateProposalResponse(hdrbytes []byte, payl []byte, response *peer.Respons
250250
return resp, nil
251251
}
252252

253+
// CreateProposalResponseFailure creates a proposal response for cases where
254+
// endorsement proposal fails either due to a endorsement failure or a chaincode
255+
// failure (chaincode response status >=500)
256+
func CreateProposalResponseFailure(hdrbytes []byte, payl []byte, response *peer.Response, results []byte, events []byte, ccid *peer.ChaincodeID, visibility []byte) (*peer.ProposalResponse, error) {
257+
hdr, err := GetHeader(hdrbytes)
258+
if err != nil {
259+
return nil, err
260+
}
261+
262+
// obtain the proposal hash given proposal header, payload and the requested visibility
263+
pHashBytes, err := GetProposalHash1(hdr, payl, visibility)
264+
if err != nil {
265+
return nil, fmt.Errorf("Could not compute proposal hash: err %s", err)
266+
}
267+
268+
// get the bytes of the proposal response payload
269+
prpBytes, err := GetBytesProposalResponsePayload(pHashBytes, response, results, events, ccid)
270+
if err != nil {
271+
return nil, errors.New("Failure while marshaling the ProposalResponsePayload")
272+
}
273+
274+
resp := &peer.ProposalResponse{
275+
// Timestamp: TODO!
276+
Payload: prpBytes,
277+
Response: &peer.Response{Status: 500, Message: "Chaincode Error"}}
278+
279+
return resp, nil
280+
}
281+
253282
// GetSignedProposal returns a signed proposal given a Proposal message and a signing identity
254283
func GetSignedProposal(prop *peer.Proposal, signer msp.SigningIdentity) (*peer.SignedProposal, error) {
255284
// check for nil argument

0 commit comments

Comments
 (0)