Skip to content

Commit d9a6bac

Browse files
committed
[FAB-9446] Peer CLI multi-endorse via string arrays
This CR add supports to the peer CLI for obtaining multiple endorsements for an "invoke" call. The peers and the paths to the TLS root cert files for the peers are supplied via the --peerAddresses and --tlsRootCertFiles string array pflags. Change-Id: I087abc34b877b386defec012e70bb8391610d227 Signed-off-by: Will Lahti <wtlahti@us.ibm.com>
1 parent 625c0cc commit d9a6bac

22 files changed

+402
-277
lines changed

peer/chaincode/chaincode.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright IBM Corp. 2016 All Rights Reserved.
2+
Copyright IBM Corp. All Rights Reserved.
33
44
SPDX-License-Identifier: Apache-2.0
55
*/
@@ -63,6 +63,8 @@ var (
6363
transient string
6464
collectionsConfigFile string
6565
collectionConfigBytes []byte
66+
peerAddresses []string
67+
tlsRootCertFiles []string
6668
)
6769

6870
var chaincodeCmd = &cobra.Command{
@@ -110,6 +112,10 @@ func resetFlags() {
110112
"Get the instantiated chaincodes on a channel")
111113
flags.StringVar(&collectionsConfigFile, "collections-config", common.UndefinedParamValue,
112114
fmt.Sprint("The file containing the configuration for the chaincode's collection"))
115+
flags.StringArrayVarP(&peerAddresses, "peerAddresses", "", []string{common.UndefinedParamValue},
116+
fmt.Sprint("The addresses of the peers to connect to"))
117+
flags.StringArrayVarP(&tlsRootCertFiles, "tlsRootCertFiles", "", []string{common.UndefinedParamValue},
118+
fmt.Sprint("If TLS is enabled, the paths to the TLS root cert files of the peers to connect to. The order and number of certs specified should match the --peerAddresses flag"))
113119
}
114120

115121
func attachFlags(cmd *cobra.Command, names []string) {

peer/chaincode/common.go

+93-44
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright IBM Corp. 2016 All Rights Reserved.
2+
Copyright IBM Corp. All Rights Reserved.
33
44
SPDX-License-Identifier: Apache-2.0
55
*/
@@ -106,7 +106,7 @@ func chaincodeInvokeOrQuery(cmd *cobra.Command, invoke bool, cf *ChaincodeCmdFac
106106
channelID,
107107
invoke,
108108
cf.Signer,
109-
cf.EndorserClient,
109+
cf.EndorserClients,
110110
cf.BroadcastClient)
111111

112112
if err != nil {
@@ -229,35 +229,35 @@ func checkChaincodeCmdParams(cmd *cobra.Command) error {
229229
if chaincodeVersion == common.UndefinedParamValue {
230230
return fmt.Errorf("chaincode version is not provided for %s", cmd.Name())
231231
}
232-
}
233232

234-
if escc != common.UndefinedParamValue {
235-
logger.Infof("Using escc %s", escc)
236-
} else {
237-
logger.Info("Using default escc")
238-
escc = "escc"
239-
}
233+
if escc != common.UndefinedParamValue {
234+
logger.Infof("Using escc %s", escc)
235+
} else {
236+
logger.Info("Using default escc")
237+
escc = "escc"
238+
}
240239

241-
if vscc != common.UndefinedParamValue {
242-
logger.Infof("Using vscc %s", vscc)
243-
} else {
244-
logger.Info("Using default vscc")
245-
vscc = "vscc"
246-
}
240+
if vscc != common.UndefinedParamValue {
241+
logger.Infof("Using vscc %s", vscc)
242+
} else {
243+
logger.Info("Using default vscc")
244+
vscc = "vscc"
245+
}
247246

248-
if policy != common.UndefinedParamValue {
249-
p, err := cauthdsl.FromString(policy)
250-
if err != nil {
251-
return fmt.Errorf("invalid policy %s", policy)
247+
if policy != common.UndefinedParamValue {
248+
p, err := cauthdsl.FromString(policy)
249+
if err != nil {
250+
return fmt.Errorf("invalid policy %s", policy)
251+
}
252+
policyMarshalled = putils.MarshalOrPanic(p)
252253
}
253-
policyMarshalled = putils.MarshalOrPanic(p)
254-
}
255254

256-
if collectionsConfigFile != common.UndefinedParamValue {
257-
var err error
258-
collectionConfigBytes, err = getCollectionConfigFromFile(collectionsConfigFile)
259-
if err != nil {
260-
return errors.WithMessage(err, fmt.Sprintf("invalid collection configuration in file %s", collectionsConfigFile))
255+
if collectionsConfigFile != common.UndefinedParamValue {
256+
var err error
257+
collectionConfigBytes, err = getCollectionConfigFromFile(collectionsConfigFile)
258+
if err != nil {
259+
return errors.WithMessage(err, fmt.Sprintf("invalid collection configuration in file %s", collectionsConfigFile))
260+
}
261261
}
262262
}
263263

@@ -291,52 +291,91 @@ func checkChaincodeCmdParams(cmd *cobra.Command) error {
291291
return nil
292292
}
293293

294+
func validatePeerConnectionParameters(cmdName string) error {
295+
// currently only support multiple peer addresses for invoke
296+
if cmdName != "invoke" && len(peerAddresses) > 1 {
297+
return errors.Errorf("'%s' command can only be executed against one peer. received %d", cmdName, len(peerAddresses))
298+
}
299+
300+
if len(tlsRootCertFiles) > len(peerAddresses) {
301+
logger.Warningf("received more TLS root cert files (%d) than peer addresses (%d)", len(tlsRootCertFiles), len(peerAddresses))
302+
}
303+
304+
if viper.GetBool("peer.tls.enabled") {
305+
if len(tlsRootCertFiles) != len(peerAddresses) {
306+
return errors.Errorf("number of peer addresses (%d) does not match the number of TLS root cert files (%d)", len(peerAddresses), len(tlsRootCertFiles))
307+
}
308+
} else {
309+
tlsRootCertFiles = nil
310+
}
311+
312+
return nil
313+
}
314+
294315
// ChaincodeCmdFactory holds the clients used by ChaincodeCmd
295316
type ChaincodeCmdFactory struct {
296-
EndorserClient pb.EndorserClient
317+
EndorserClients []pb.EndorserClient
297318
Signer msp.SigningIdentity
298319
BroadcastClient common.BroadcastClient
299320
}
300321

301322
// InitCmdFactory init the ChaincodeCmdFactory with default clients
302-
func InitCmdFactory(isEndorserRequired, isOrdererRequired bool) (*ChaincodeCmdFactory, error) {
323+
func InitCmdFactory(cmdName string, isEndorserRequired, isOrdererRequired bool) (*ChaincodeCmdFactory, error) {
303324
var err error
304-
var endorserClient pb.EndorserClient
325+
var endorserClients []pb.EndorserClient
305326
if isEndorserRequired {
306-
endorserClient, err = common.GetEndorserClientFnc()
307-
if err != nil {
308-
return nil, fmt.Errorf("error getting endorser client %s: %s", chainFuncName, err)
327+
if err = validatePeerConnectionParameters(cmdName); err != nil {
328+
return nil, errors.WithMessage(err, "error validating peer connection parameters")
329+
}
330+
for i, address := range peerAddresses {
331+
var tlsRootCertFile string
332+
if tlsRootCertFiles != nil {
333+
tlsRootCertFile = tlsRootCertFiles[i]
334+
}
335+
endorserClient, err := common.GetEndorserClientFnc(address, tlsRootCertFile)
336+
if err != nil {
337+
return nil, errors.WithMessage(err, fmt.Sprintf("error getting endorser client for %s", cmdName))
338+
}
339+
endorserClients = append(endorserClients, endorserClient)
340+
}
341+
if len(endorserClients) == 0 {
342+
return nil, errors.New("no endorser clients retrieved - this might indicate a bug")
309343
}
310344
}
311345

312346
signer, err := common.GetDefaultSignerFnc()
313347
if err != nil {
314-
return nil, fmt.Errorf("error getting default signer: %s", err)
348+
return nil, errors.WithMessage(err, "error getting default signer")
315349
}
316350

317351
var broadcastClient common.BroadcastClient
318352
if isOrdererRequired {
319353
if len(common.OrderingEndpoint) == 0 {
354+
if len(endorserClients) == 0 {
355+
return nil, errors.New("orderer is required, but no ordering endpoint or endorser client supplied")
356+
}
357+
endorserClient := endorserClients[0]
358+
320359
orderingEndpoints, err := common.GetOrdererEndpointOfChainFnc(channelID, signer, endorserClient)
321360
if err != nil {
322-
return nil, fmt.Errorf("error getting (%s) orderer endpoint: %s", channelID, err)
361+
return nil, errors.WithMessage(err, fmt.Sprintf("error getting channel (%s) orderer endpoint", channelID))
323362
}
324363
if len(orderingEndpoints) == 0 {
325-
return nil, fmt.Errorf("error no orderer endpoint got for %s", channelID)
364+
return nil, errors.Errorf("no orderer endpoints retrieved for channel %s", channelID)
326365
}
327-
logger.Infof("Get chain(%s) orderer endpoint: %s", channelID, orderingEndpoints[0])
366+
logger.Infof("Retrieved channel (%s) orderer endpoint: %s", channelID, orderingEndpoints[0])
328367
// override viper env
329368
viper.Set("orderer.address", orderingEndpoints[0])
330369
}
331370

332371
broadcastClient, err = common.GetBroadcastClientFnc()
333372

334373
if err != nil {
335-
return nil, fmt.Errorf("error getting broadcast client: %s", err)
374+
return nil, errors.WithMessage(err, "error getting broadcast client")
336375
}
337376
}
338377
return &ChaincodeCmdFactory{
339-
EndorserClient: endorserClient,
378+
EndorserClients: endorserClients,
340379
Signer: signer,
341380
BroadcastClient: broadcastClient,
342381
}, nil
@@ -356,7 +395,7 @@ func ChaincodeInvokeOrQuery(
356395
cID string,
357396
invoke bool,
358397
signer msp.SigningIdentity,
359-
endorserClient pb.EndorserClient,
398+
endorserClients []pb.EndorserClient,
360399
bc common.BroadcastClient,
361400
) (*pb.ProposalResponse, error) {
362401
// Build the ChaincodeInvocationSpec message
@@ -394,20 +433,30 @@ func ChaincodeInvokeOrQuery(
394433
if err != nil {
395434
return nil, fmt.Errorf("error creating signed proposal %s: %s", funcName, err)
396435
}
436+
var responses []*pb.ProposalResponse
437+
for _, endorser := range endorserClients {
438+
proposalResp, err := endorser.ProcessProposal(context.Background(), signedProp)
439+
if err != nil {
440+
return nil, fmt.Errorf("error endorsing %s: %s", funcName, err)
441+
}
442+
responses = append(responses, proposalResp)
443+
}
397444

398-
var proposalResp *pb.ProposalResponse
399-
proposalResp, err = endorserClient.ProcessProposal(context.Background(), signedProp)
400-
if err != nil {
401-
return nil, fmt.Errorf("error endorsing %s: %s", funcName, err)
445+
if len(responses) == 0 {
446+
// this should only happen if some new code has introduced a bug
447+
return nil, errors.New("no proposal responses received - this might indicate a bug")
402448
}
449+
// all responses will be checked when the signed transaction is created.
450+
// for now, just set this so we check the first response's status
451+
proposalResp := responses[0]
403452

404453
if invoke {
405454
if proposalResp != nil {
406455
if proposalResp.Response.Status >= shim.ERROR {
407456
return proposalResp, nil
408457
}
409458
// assemble a signed transaction (it's an Envelope message)
410-
env, err := putils.CreateSignedTx(prop, signer, proposalResp)
459+
env, err := putils.CreateSignedTx(prop, signer, responses...)
411460
if err != nil {
412461
return proposalResp, fmt.Errorf("could not assemble transaction, err %s", err)
413462
}

peer/chaincode/common_test.go

+106-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
/*
2-
Copyright Digital Asset Holdings, LLC 2016 All Rights Reserved.
2+
Copyright Digital Asset Holdings, LLC. All Rights Reserved.
3+
Copyright IBM Corp. All Rights Reserved.
34
4-
Licensed under the Apache License, Version 2.0 (the "License");
5-
you may not use this file except in compliance with the License.
6-
You may obtain a copy of the License at
7-
8-
http://www.apache.org/licenses/LICENSE-2.0
9-
10-
Unless required by applicable law or agreed to in writing, software
11-
distributed under the License is distributed on an "AS IS" BASIS,
12-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
See the License for the specific language governing permissions and
14-
limitations under the License.
5+
SPDX-License-Identifier: Apache-2.0
156
*/
167

178
package chaincode
189

1910
import (
2011
"encoding/json"
12+
"fmt"
2113
"testing"
2214

2315
"github.com/golang/protobuf/proto"
@@ -26,11 +18,13 @@ import (
2618
"github.com/hyperledger/fabric/common/tools/configtxgen/configtxgentest"
2719
"github.com/hyperledger/fabric/common/tools/configtxgen/encoder"
2820
genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig"
21+
"github.com/hyperledger/fabric/core/config/configtest"
2922
"github.com/hyperledger/fabric/peer/common"
3023
common2 "github.com/hyperledger/fabric/protos/common"
3124
pb "github.com/hyperledger/fabric/protos/peer"
3225
"github.com/hyperledger/fabric/protos/utils"
3326
"github.com/spf13/cobra"
27+
"github.com/spf13/viper"
3428
"github.com/stretchr/testify/assert"
3529
"github.com/stretchr/testify/require"
3630
)
@@ -217,3 +211,103 @@ func TestCollectionParsing(t *testing.T) {
217211
assert.Error(t, err)
218212
assert.Nil(t, cc)
219213
}
214+
215+
func TestValidatePeerConnectionParams(t *testing.T) {
216+
assert := assert.New(t)
217+
viper.Reset()
218+
cleanup := configtest.SetDevFabricConfigPath(t)
219+
defer cleanup()
220+
221+
// TLS disabled
222+
viper.Set("peer.tls.enabled", false)
223+
224+
// failure - more than one peer and TLS root cert - not invoke
225+
resetFlags()
226+
peerAddresses = []string{"peer0", "peer1"}
227+
tlsRootCertFiles = []string{"cert0", "cert1"}
228+
err := validatePeerConnectionParameters("query")
229+
assert.Error(err)
230+
assert.Contains(err.Error(), "command can only be executed against one peer")
231+
232+
// success - peer provided and no TLS root certs
233+
// TLS disabled
234+
resetFlags()
235+
peerAddresses = []string{"peer0"}
236+
err = validatePeerConnectionParameters("query")
237+
assert.NoError(err)
238+
assert.Nil(tlsRootCertFiles)
239+
240+
// success - more TLS root certs than peers
241+
// TLS disabled
242+
resetFlags()
243+
peerAddresses = []string{"peer0"}
244+
tlsRootCertFiles = []string{"cert0", "cert1"}
245+
err = validatePeerConnectionParameters("invoke")
246+
assert.NoError(err)
247+
assert.Nil(tlsRootCertFiles)
248+
249+
// success - multiple peers and no TLS root certs - invoke
250+
// TLS disabled
251+
resetFlags()
252+
peerAddresses = []string{"peer0", "peer1"}
253+
err = validatePeerConnectionParameters("invoke")
254+
assert.NoError(err)
255+
assert.Nil(tlsRootCertFiles)
256+
257+
// TLS enabled
258+
viper.Set("peer.tls.enabled", true)
259+
260+
// failure - uneven number of peers and TLS root certs - invoke
261+
// TLS enabled
262+
resetFlags()
263+
peerAddresses = []string{"peer0", "peer1"}
264+
tlsRootCertFiles = []string{"cert0"}
265+
err = validatePeerConnectionParameters("invoke")
266+
assert.Error(err)
267+
assert.Contains(err.Error(), fmt.Sprintf("number of peer addresses (%d) does not match the number of TLS root cert files (%d)", len(peerAddresses), len(tlsRootCertFiles)))
268+
269+
// success - more than one peer and TLS root certs - invoke
270+
// TLS enabled
271+
resetFlags()
272+
peerAddresses = []string{"peer0", "peer1"}
273+
tlsRootCertFiles = []string{"cert0", "cert1"}
274+
err = validatePeerConnectionParameters("invoke")
275+
assert.NoError(err)
276+
277+
// cleanup pflags and viper
278+
resetFlags()
279+
viper.Reset()
280+
}
281+
282+
func TestInitCmdFactoryFailures(t *testing.T) {
283+
assert := assert.New(t)
284+
285+
// failure validating peer connection parameters
286+
resetFlags()
287+
peerAddresses = []string{"peer0", "peer1"}
288+
tlsRootCertFiles = []string{"cert0", "cert1"}
289+
cf, err := InitCmdFactory("query", true, false)
290+
assert.Error(err)
291+
assert.Contains(err.Error(), "error validating peer connection parameters: 'query' command can only be executed against one peer")
292+
assert.Nil(cf)
293+
294+
// failure - no peers supplied and endorser client is needed
295+
resetFlags()
296+
peerAddresses = []string{}
297+
cf, err = InitCmdFactory("query", true, false)
298+
assert.Error(err)
299+
assert.Contains(err.Error(), "no endorser clients retrieved")
300+
assert.Nil(cf)
301+
302+
// failure - orderer client is needed, ordering endpoint is empty and no
303+
// endorser client supplied
304+
resetFlags()
305+
peerAddresses = nil
306+
cf, err = InitCmdFactory("invoke", false, true)
307+
assert.Error(err)
308+
assert.Contains(err.Error(), "no ordering endpoint or endorser client supplied")
309+
assert.Nil(cf)
310+
311+
// cleanup
312+
resetFlags()
313+
}

0 commit comments

Comments
 (0)