Skip to content

Commit 682e709

Browse files
author
Jason Yellick
committed
FAB-16353 Move proposal parsing out of eplugin
Presently, the endorser plugin contract does some static parsing and error handling for the proposal which is duplicative in some ways of what the endorser.go validation does. Because this logic is not per plugin, there is no reason it needs to be tied with the plugin provider. So, this CR moves it back into endorser.go so that the common logic can be factored out. Change-Id: Ic9451b44eba5c44a3e8ba0bcfb9d409454b68b00 Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
1 parent 76905a7 commit 682e709

File tree

3 files changed

+61
-190
lines changed

3 files changed

+61
-190
lines changed

core/endorser/endorser.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ type Support interface {
6767
CheckACL(channelID string, signedProp *pb.SignedProposal) error
6868

6969
// EndorseWithPlugin endorses the response with a plugin
70-
EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error)
70+
EndorseWithPlugin(pluginName, channnelID string, prpBytes []byte, signedProposal *pb.SignedProposal) (*pb.Endorsement, []byte, error)
7171

7272
// GetLedgerHeight returns ledger height for given channelID
7373
GetLedgerHeight(channelID string) (uint64, error)
@@ -230,26 +230,48 @@ func (e *Endorser) endorseProposal(up *UnpackedProposal, response *pb.Response,
230230
endorserLogger.Debugf("[%s][%s] Entry chaincode: %s", up.ChannelHeader.ChannelId, shorttxid(up.ChannelHeader.TxId), up.ChaincodeName)
231231
defer endorserLogger.Debugf("[%s][%s] Exit", up.ChannelHeader.ChannelId, shorttxid(up.ChannelHeader.TxId))
232232

233+
if response.Status >= shim.ERRORTHRESHOLD {
234+
return &pb.ProposalResponse{Response: response}, nil
235+
}
236+
237+
hdr, err := protoutil.UnmarshalHeader(up.Proposal.Header)
238+
if err != nil {
239+
endorserLogger.Warning("Failed parsing header", err)
240+
return nil, errors.Wrap(err, "failed parsing header")
241+
}
242+
243+
pHashBytes, err := protoutil.GetProposalHash1(hdr, up.Proposal.Payload)
244+
if err != nil {
245+
endorserLogger.Warning("Failed computing proposal hash", err)
246+
return nil, errors.Wrap(err, "could not compute proposal hash")
247+
}
248+
249+
prpBytes, err := protoutil.GetBytesProposalResponsePayload(pHashBytes, response, simRes, eventBytes, &pb.ChaincodeID{
250+
Name: up.ChaincodeName,
251+
Version: cd.CCVersion(),
252+
})
253+
if err != nil {
254+
endorserLogger.Warning("Failed marshaling the proposal response payload to bytes", err)
255+
return nil, errors.New("failure while marshaling the ProposalResponsePayload")
256+
}
257+
233258
escc := cd.Endorsement()
234259

235260
endorserLogger.Debugf("[%s][%s] escc for chaincode %s is %s", up.ChannelHeader.ChannelId, shorttxid(up.ChannelHeader.TxId), up.ChaincodeName, escc)
236261

237-
ctx := Context{
238-
PluginName: escc,
239-
Channel: up.ChannelHeader.ChannelId,
240-
SignedProposal: up.SignedProposal,
241-
ChaincodeID: &pb.ChaincodeID{
242-
Name: up.ChaincodeName,
243-
Version: cd.CCVersion(),
244-
},
245-
Event: eventBytes,
246-
SimRes: simRes,
247-
Response: response,
248-
// Visibility is checked to be nil at the beginning of the flow, so it is safe not to set
249-
Proposal: up.Proposal,
250-
TxID: up.ChannelHeader.TxId,
251-
}
252-
return e.s.EndorseWithPlugin(ctx)
262+
endorsement, prpBytes, err := e.s.EndorseWithPlugin(escc, up.ChannelHeader.ChannelId, prpBytes, up.SignedProposal)
263+
if err != nil {
264+
return nil, errors.WithMessage(err, "endorsing with plugin failed")
265+
}
266+
267+
resp := &pb.ProposalResponse{
268+
Version: 1,
269+
Endorsement: endorsement,
270+
Payload: prpBytes,
271+
Response: response,
272+
}
273+
274+
return resp, nil
253275
}
254276

255277
// preProcess checks the tx proposal headers, uniqueness and ACL

core/endorser/plugin_endorser.go

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@ import (
1010
"fmt"
1111
"sync"
1212

13-
"github.com/hyperledger/fabric/core/chaincode/shim"
1413
endorsement "github.com/hyperledger/fabric/core/handlers/endorsement/api"
1514
endorsement3 "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities"
1615
"github.com/hyperledger/fabric/core/transientstore"
1716
pb "github.com/hyperledger/fabric/protos/peer"
18-
"github.com/hyperledger/fabric/protoutil"
1917
"github.com/pkg/errors"
2018
)
2119

@@ -165,43 +163,13 @@ type PluginEndorser struct {
165163
}
166164

167165
// EndorseWithPlugin endorses the response with a plugin
168-
func (pe *PluginEndorser) EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error) {
169-
endorserLogger.Debug("Entering endorsement for", ctx)
170-
171-
if ctx.Response == nil {
172-
return nil, errors.New("response is nil")
173-
}
174-
175-
if ctx.Response.Status >= shim.ERRORTHRESHOLD {
176-
return &pb.ProposalResponse{Response: ctx.Response}, nil
177-
}
178-
179-
plugin, err := pe.getOrCreatePlugin(PluginName(ctx.PluginName), ctx.Channel)
180-
if err != nil {
181-
endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err)
182-
return nil, errors.Errorf("plugin with name %s could not be used: %v", ctx.PluginName, err)
183-
}
184-
185-
prpBytes, err := proposalResponsePayloadFromContext(ctx)
186-
if err != nil {
187-
endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err)
188-
return nil, errors.Wrap(err, "failed assembling proposal response payload")
189-
}
190-
191-
endorsement, prpBytes, err := plugin.Endorse(prpBytes, ctx.SignedProposal)
166+
func (pe *PluginEndorser) EndorseWithPlugin(pluginName, channelID string, prpBytes []byte, signedProposal *pb.SignedProposal) (*pb.Endorsement, []byte, error) {
167+
plugin, err := pe.getOrCreatePlugin(PluginName(pluginName), channelID)
192168
if err != nil {
193-
endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err)
194-
return nil, errors.WithStack(err)
169+
return nil, nil, errors.WithMessagef(err, "plugin with name %s could not be used", pluginName)
195170
}
196171

197-
resp := &pb.ProposalResponse{
198-
Version: 1,
199-
Endorsement: endorsement,
200-
Payload: prpBytes,
201-
Response: ctx.Response,
202-
}
203-
endorserLogger.Debug("Exiting", ctx)
204-
return resp, nil
172+
return plugin.Endorse(prpBytes, signedProposal)
205173
}
206174

207175
// getOrCreatePlugin returns a plugin instance for the given plugin name and channel
@@ -229,24 +197,3 @@ func (pe *PluginEndorser) getOrCreatePluginChannelMapping(plugin PluginName, pf
229197
}
230198
return endorserChannelMapping
231199
}
232-
233-
func proposalResponsePayloadFromContext(ctx Context) ([]byte, error) {
234-
hdr, err := protoutil.UnmarshalHeader(ctx.Proposal.Header)
235-
if err != nil {
236-
endorserLogger.Warning("Failed parsing header", err)
237-
return nil, errors.Wrap(err, "failed parsing header")
238-
}
239-
240-
pHashBytes, err := protoutil.GetProposalHash1(hdr, ctx.Proposal.Payload)
241-
if err != nil {
242-
endorserLogger.Warning("Failed computing proposal hash", err)
243-
return nil, errors.Wrap(err, "could not compute proposal hash")
244-
}
245-
246-
prpBytes, err := protoutil.GetBytesProposalResponsePayload(pHashBytes, ctx.Response, ctx.SimRes, ctx.Event, ctx.ChaincodeID)
247-
if err != nil {
248-
endorserLogger.Warning("Failed marshaling the proposal response payload to bytes", err)
249-
return nil, errors.New("failure while marshaling the ProposalResponsePayload")
250-
}
251-
return prpBytes, nil
252-
}

core/endorser/plugin_endorser_test.go

Lines changed: 18 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@ import (
1111

1212
"github.com/golang/protobuf/proto"
1313
"github.com/hyperledger/fabric/common/mocks/ledger"
14-
"github.com/hyperledger/fabric/core/chaincode/shim"
1514
"github.com/hyperledger/fabric/core/endorser"
1615
"github.com/hyperledger/fabric/core/endorser/mocks"
1716
endorsement "github.com/hyperledger/fabric/core/handlers/endorsement/api"
1817
. "github.com/hyperledger/fabric/core/handlers/endorsement/api/state"
1918
"github.com/hyperledger/fabric/core/transientstore"
20-
"github.com/hyperledger/fabric/protos/common"
2119
"github.com/hyperledger/fabric/protos/ledger/rwset"
2220
"github.com/hyperledger/fabric/protos/peer"
2321
transientstore2 "github.com/hyperledger/fabric/protos/transientstore"
24-
"github.com/hyperledger/fabric/protoutil"
2522
"github.com/pkg/errors"
2623
"github.com/stretchr/testify/assert"
2724
"github.com/stretchr/testify/mock"
@@ -38,21 +35,13 @@ func TestPluginEndorserNotFound(t *testing.T) {
3835
pluginEndorser := endorser.NewPluginEndorser(&endorser.PluginSupport{
3936
PluginMapper: pluginMapper,
4037
})
41-
resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{
42-
Response: &peer.Response{},
43-
PluginName: "notfound",
44-
})
45-
assert.Nil(t, resp)
38+
endorsement, prpBytes, err := pluginEndorser.EndorseWithPlugin("notfound", "", nil, nil)
39+
assert.Nil(t, endorsement)
40+
assert.Nil(t, prpBytes)
4641
assert.Contains(t, err.Error(), "plugin with name notfound wasn't found")
4742
}
4843

4944
func TestPluginEndorserGreenPath(t *testing.T) {
50-
proposal, _, err := protoutil.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, "mychannel", &peer.ChaincodeInvocationSpec{
51-
ChaincodeSpec: &peer.ChaincodeSpec{
52-
ChaincodeId: &peer.ChaincodeID{Name: "mycc"},
53-
},
54-
}, []byte{1, 2, 3})
55-
assert.NoError(t, err)
5645
expectedSignature := []byte{5, 4, 3, 2, 1}
5746
expectedProposalResponsePayload := []byte{1, 2, 3}
5847
pluginMapper := &mocks.PluginMapper{}
@@ -73,48 +62,35 @@ func TestPluginEndorserGreenPath(t *testing.T) {
7362
PluginMapper: pluginMapper,
7463
TransientStoreRetriever: mockTransientStoreRetriever,
7564
})
76-
ctx := endorser.Context{
77-
Response: &peer.Response{},
78-
PluginName: "plugin",
79-
Proposal: proposal,
80-
ChaincodeID: &peer.ChaincodeID{
81-
Name: "mycc",
82-
},
83-
Channel: "mychannel",
84-
}
8565

8666
// Scenario I: Call the endorsement for the first time
87-
resp, err := pluginEndorser.EndorseWithPlugin(ctx)
67+
endorsement, prpBytes, err := pluginEndorser.EndorseWithPlugin("plugin", "mychannel", nil, nil)
8868
assert.NoError(t, err)
89-
assert.NotNil(t, resp)
90-
assert.Equal(t, expectedSignature, resp.Endorsement.Signature)
91-
assert.Equal(t, expectedProposalResponsePayload, resp.Payload)
69+
assert.Equal(t, expectedSignature, endorsement.Signature)
70+
assert.Equal(t, expectedProposalResponsePayload, prpBytes)
9271
// Ensure both state and SigningIdentityFetcher were passed to Init()
9372
plugin.AssertCalled(t, "Init", &endorser.ChannelState{QueryCreator: queryCreator, Store: mockTransientStore}, sif)
9473

9574
// Scenario II: Call the endorsement again a second time.
9675
// Ensure the plugin wasn't instantiated again - which means the same instance
9776
// was used to service the request.
9877
// Also - check that the Init() wasn't called more than once on the plugin.
99-
resp, err = pluginEndorser.EndorseWithPlugin(ctx)
78+
endorsement, prpBytes, err = pluginEndorser.EndorseWithPlugin("plugin", "mychannel", nil, nil)
10079
assert.NoError(t, err)
101-
assert.NotNil(t, resp)
102-
assert.Equal(t, expectedSignature, resp.Endorsement.Signature)
103-
assert.Equal(t, expectedProposalResponsePayload, resp.Payload)
80+
assert.Equal(t, expectedSignature, endorsement.Signature)
81+
assert.Equal(t, expectedProposalResponsePayload, prpBytes)
10482
pluginFactory.AssertNumberOfCalls(t, "New", 1)
10583
plugin.AssertNumberOfCalls(t, "Init", 1)
10684

10785
// Scenario III: Call the endorsement with a channel-less context.
10886
// The init method should be called again, but this time - a channel state object
10987
// should not be passed into the init.
110-
ctx.Channel = ""
11188
pluginFactory.On("New").Return(plugin).Once()
11289
plugin.On("Init", mock.Anything).Return(nil).Once()
113-
resp, err = pluginEndorser.EndorseWithPlugin(ctx)
90+
endorsement, prpBytes, err = pluginEndorser.EndorseWithPlugin("plugin", "", nil, nil)
11491
assert.NoError(t, err)
115-
assert.NotNil(t, resp)
116-
assert.Equal(t, expectedSignature, resp.Endorsement.Signature)
117-
assert.Equal(t, expectedProposalResponsePayload, resp.Payload)
92+
assert.Equal(t, expectedSignature, endorsement.Signature)
93+
assert.Equal(t, expectedProposalResponsePayload, prpBytes)
11894
plugin.AssertCalled(t, "Init", sif)
11995
}
12096

@@ -136,73 +112,15 @@ func TestPluginEndorserErrors(t *testing.T) {
136112
TransientStoreRetriever: mockTransientStoreRetriever,
137113
})
138114

139-
// Scenario I: Failed initializing plugin
115+
// Failed initializing plugin
140116
t.Run("PluginInitializationFailure", func(t *testing.T) {
141117
plugin.On("Init", mock.Anything, mock.Anything).Return(errors.New("plugin initialization failed")).Once()
142-
resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{
143-
PluginName: "plugin",
144-
Channel: "mychannel",
145-
Response: &peer.Response{},
146-
})
147-
assert.Nil(t, resp)
118+
endorsement, prpBytes, err := pluginEndorser.EndorseWithPlugin("plugin", "mychannel", nil, nil)
119+
assert.Nil(t, endorsement)
120+
assert.Nil(t, prpBytes)
148121
assert.Contains(t, err.Error(), "plugin initialization failed")
149122
})
150123

151-
// Scenario II: an empty proposal is passed in the context, and parsing fails
152-
t.Run("EmptyProposal", func(t *testing.T) {
153-
plugin.On("Init", mock.Anything, mock.Anything).Return(nil).Once()
154-
ctx := endorser.Context{
155-
Response: &peer.Response{},
156-
PluginName: "plugin",
157-
ChaincodeID: &peer.ChaincodeID{
158-
Name: "mycc",
159-
},
160-
Proposal: &peer.Proposal{},
161-
Channel: "mychannel",
162-
}
163-
resp, err := pluginEndorser.EndorseWithPlugin(ctx)
164-
assert.Nil(t, resp)
165-
assert.Contains(t, err.Error(), "could not compute proposal hash")
166-
})
167-
168-
// Scenario III: The proposal's header is invalid
169-
t.Run("InvalidHeader in the proposal", func(t *testing.T) {
170-
ctx := endorser.Context{
171-
Response: &peer.Response{},
172-
PluginName: "plugin",
173-
ChaincodeID: &peer.ChaincodeID{
174-
Name: "mycc",
175-
},
176-
Proposal: &peer.Proposal{
177-
Header: []byte{1, 2, 3},
178-
},
179-
Channel: "mychannel",
180-
}
181-
resp, err := pluginEndorser.EndorseWithPlugin(ctx)
182-
assert.Nil(t, resp)
183-
assert.Contains(t, err.Error(), "failed parsing header")
184-
})
185-
186-
// Scenario IV: The proposal's response status code indicates an error
187-
t.Run("ResponseStatusContainsError", func(t *testing.T) {
188-
r := &peer.Response{
189-
Status: shim.ERRORTHRESHOLD,
190-
Payload: []byte{1, 2, 3},
191-
Message: "bla bla",
192-
}
193-
resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{
194-
Response: r,
195-
})
196-
assert.Equal(t, &peer.ProposalResponse{Response: r}, resp)
197-
assert.NoError(t, err)
198-
})
199-
200-
// Scenario V: The proposal's response is nil
201-
t.Run("ResponseIsNil", func(t *testing.T) {
202-
resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{})
203-
assert.Nil(t, resp)
204-
assert.Contains(t, err.Error(), "response is nil")
205-
})
206124
}
207125

208126
func transientStoreRetriever() *mocks.TransientStoreRetriever {
@@ -281,22 +199,6 @@ func TestTransientStore(t *testing.T) {
281199
TransientStoreRetriever: storeRetriever,
282200
})
283201

284-
proposal, _, err := protoutil.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, "mychannel", &peer.ChaincodeInvocationSpec{
285-
ChaincodeSpec: &peer.ChaincodeSpec{
286-
ChaincodeId: &peer.ChaincodeID{Name: "mycc"},
287-
},
288-
}, []byte{1, 2, 3})
289-
assert.NoError(t, err)
290-
ctx := endorser.Context{
291-
Response: &peer.Response{},
292-
PluginName: "plugin",
293-
Proposal: proposal,
294-
ChaincodeID: &peer.ChaincodeID{
295-
Name: "mycc",
296-
},
297-
Channel: "mychannel",
298-
}
299-
300202
rws := &rwset.TxPvtReadWriteSet{
301203
NsPvtRwset: []*rwset.NsPvtReadWriteSet{
302204
{
@@ -316,11 +218,11 @@ func TestTransientStore(t *testing.T) {
316218

317219
transientStore.On("GetTxPvtRWSetByTxid", mock.Anything, mock.Anything).Return(scanner, nil)
318220

319-
resp, err := pluginEndorser.EndorseWithPlugin(ctx)
221+
_, prpBytes, err := pluginEndorser.EndorseWithPlugin("plugin", "mychannel", nil, nil)
320222
assert.NoError(t, err)
321223

322224
txrws := &rwset.TxPvtReadWriteSet{}
323-
err = proto.Unmarshal(resp.Payload, txrws)
225+
err = proto.Unmarshal(prpBytes, txrws)
324226
assert.NoError(t, err)
325227
assert.True(t, proto.Equal(rws, txrws))
326228
scanner.AssertCalled(t, "Close")

0 commit comments

Comments
 (0)