Skip to content

Commit

Permalink
[FAB-6230] peer CLI support for cfg lifecycle
Browse files Browse the repository at this point in the history
This change set:

- Extends LSCC.GETINSTALLEDCHAINCODES to return also the ID to be used
  for the config update Txn by extending the protos/peer/query.proto
  and adding an ID for a chaincode.
- Adds a new ability to the peer CLI- to support instantiation\upgrade
   in the config lifecycle way.
  the flow now is:
  - The peer CLI queries CSCC to obtain the resource config.
    - If the a bad status (not OK) is returned it means
      the channel doesn't have the capability and it resumes
      the flow of the old instantiate.
  - If a good (OK) status is returned, it queries LSCC
      and searches for the chaincode's ID (hashes of code and metadata)

  - Assuming it is found, it uses the old config fetched
    from the peer, and the ID of the chaincode,
    and the CLI parameters to build the new config, and submits
    it to the orderer as a resource update Txn.
  - Afterwards, it continues the usual flow of instantiate
    and upgrade (invoking LSCC and init of the target chaincode)

  - It can also:
    - Instead of sending the config update, save it to disk to be
      passed for another person.
      This is via the --resourceEnvelopeSavePath flag (-S)
    - Loaded from disk for appending a signature and optionally
      afterwards - sending the new config transaction
      for ordering.
      this is via the --resourceEnvelopeLoadPath flag (-L)

Change-Id: I0f28a4f55249b7cd60d6d06e21ddb5267721a802
Signed-off-by: yacovm <yacovm@il.ibm.com>
  • Loading branch information
yacovm committed Dec 10, 2017
1 parent 554b8d5 commit 6b636f9
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 122 deletions.
44 changes: 25 additions & 19 deletions peer/chaincode/chaincode.go
Expand Up @@ -50,24 +50,26 @@ func Cmd(cf *ChaincodeCmdFactory) *cobra.Command {

// Chaincode-related variables.
var (
chaincodeLang string
chaincodeCtorJSON string
chaincodePath string
chaincodeName string
chaincodeUsr string // Not used
chaincodeQueryRaw bool
chaincodeQueryHex bool
customIDGenAlg string
channelID string
chaincodeVersion string
policy string
escc string
vscc string
policyMarshalled []byte
orderingEndpoint string
tls bool
caFile string
transient string
chaincodeLang string
chaincodeCtorJSON string
chaincodePath string
chaincodeName string
chaincodeUsr string // Not used
chaincodeQueryRaw bool
chaincodeQueryHex bool
customIDGenAlg string
channelID string
chaincodeVersion string
policy string
escc string
vscc string
policyMarshalled []byte
orderingEndpoint string
tls bool
caFile string
transient string
resourceEnvelopeSavePath string
resourceEnvelopeLoadPath string
)

var chaincodeCmd = &cobra.Command{
Expand Down Expand Up @@ -108,6 +110,10 @@ func resetFlags() {
fmt.Sprint("The name of the endorsement system chaincode to be used for this chaincode"))
flags.StringVarP(&vscc, "vscc", "V", common.UndefinedParamValue,
fmt.Sprint("The name of the verification system chaincode to be used for this chaincode"))
flags.StringVarP(&resourceEnvelopeSavePath, "resourceEnvelopeSavePath", "S", common.UndefinedParamValue,
fmt.Sprint("Specifies the file to save the resource config update to. If not specified, sends config update"))
flags.StringVarP(&resourceEnvelopeLoadPath, "resourceEnvelopeLoadPath", "L", common.UndefinedParamValue,
fmt.Sprint("Specifies the file to load the resource config update from. If not specified, creates a new config update"))
flags.BoolVarP(&getInstalledChaincodes, "installed", "", false,
"Get the installed chaincodes on a peer")
flags.BoolVarP(&getInstantiatedChaincodes, "instantiated", "", false,
Expand All @@ -120,7 +126,7 @@ func attachFlags(cmd *cobra.Command, names []string) {
if flag := flags.Lookup(name); flag != nil {
cmdFlags.AddFlag(flag)
} else {
logger.Fatalf("Could not find flag '%s' to attach to commond '%s'", name, cmd.Name())
logger.Fatalf("Could not find flag '%s' to attach to command '%s'", name, cmd.Name())
}
}
}
50 changes: 32 additions & 18 deletions peer/chaincode/instantiate.go
Expand Up @@ -17,12 +17,12 @@ limitations under the License.
package chaincode

import (
"errors"
"fmt"

protcommon "github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/common"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
Expand All @@ -41,7 +41,17 @@ func instantiateCmd(cf *ChaincodeCmdFactory) *cobra.Command {
Long: fmt.Sprint(instantiateDesc),
ValidArgs: []string{"1"},
RunE: func(cmd *cobra.Command, args []string) error {
return chaincodeDeploy(cmd, args, cf)
cf1 := cf
if cf1 == nil {
var err error
cf1, err = InitCmdFactory(true, true)
if err != nil {
return err
}
}
return chaincodeDeploy(cf1, func() error {
return lsccInstantiate(cmd, cf1)
})
},
}
flagList := []string{
Expand All @@ -53,14 +63,15 @@ func instantiateCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"policy",
"escc",
"vscc",
"resourceEnvelopeSavePath",
"resourceEnvelopeLoadPath",
}
attachFlags(chaincodeInstantiateCmd, flagList)

return chaincodeInstantiateCmd
}

//instantiate the command via Endorser
func instantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) (*protcommon.Envelope, error) {
func instantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) (*common.Envelope, error) {
spec, err := getChaincodeSpec(cmd)
if err != nil {
return nil, err
Expand Down Expand Up @@ -108,26 +119,29 @@ func instantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) (*protcommon.Envel
// chaincodeDeploy instantiates the chaincode. On success, the chaincode name
// (hash) is printed to STDOUT for use by subsequent chaincode-related CLI
// commands.
func chaincodeDeploy(cmd *cobra.Command, args []string, cf *ChaincodeCmdFactory) error {
func chaincodeDeploy(cf *ChaincodeCmdFactory, sendInit sendInitTransaction) error {
if channelID == "" {
return errors.New("The required parameter 'channelID' is empty. Rerun the command with -C flag")
}
var err error
if cf == nil {
cf, err = InitCmdFactory(true, true)
if err != nil {
return err
}
}

defer cf.BroadcastClient.Close()
env, err := instantiate(cmd, cf)

ss := &sigSupport{cf.Signer}
version, config, err := fetchResourceConfig(cf.EndorserClient, ss, channelID)
if err != nil {
return err
return errors.Wrap(err, "failed probing channel version")
}

if env != nil {
err = cf.BroadcastClient.Send(env)
if version == v11 {
return configBasedLifecycleUpdate(ss, cf, config, sendInit)
}
return sendInit()
}

return err
func lsccInstantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) error {
env, err := instantiate(cmd, cf)
if err != nil {
return err
}
return cf.BroadcastClient.Send(env)
}
19 changes: 15 additions & 4 deletions peer/chaincode/instantiate_test.go
Expand Up @@ -17,16 +17,27 @@
package chaincode

import (
"errors"
"testing"

"github.com/hyperledger/fabric/peer/common"
"github.com/hyperledger/fabric/protos/peer"
"github.com/stretchr/testify/assert"
)

func TestInstantiateCmd(t *testing.T) {
InitMSP()

mockCF, err := getMockChaincodeCmdFactory()
assert.NoError(t, err, "Error getting mock chaincode command factory")
newMockCF := func() *ChaincodeCmdFactory {
mockCF, err := getMockChaincodeCmdFactoryWithEnorserResponses(common.MockResponse{Error: errors.New("chaincode error")}, common.MockResponse{
Response: &peer.ProposalResponse{
Response: &peer.Response{Status: 200},
Endorsement: &peer.Endorsement{},
},
})
assert.NoError(t, err, "Error getting mock chaincode command factory")
return mockCF
}

// basic function tests
var tests = []struct {
Expand Down Expand Up @@ -75,10 +86,10 @@ func TestInstantiateCmd(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resetFlags()
cmd := instantiateCmd(mockCF)
cmd := instantiateCmd(newMockCF())
addFlags(cmd)
cmd.SetArgs(test.args)
err = cmd.Execute()
err := cmd.Execute()
checkError(t, err, test.errorExpected, test.errMsg)
})
}
Expand Down
35 changes: 14 additions & 21 deletions peer/chaincode/invoke_test.go
Expand Up @@ -179,38 +179,31 @@ func TestInvokeCmdEndorsementFailure(t *testing.T) {

// Returns mock chaincode command factory
func getMockChaincodeCmdFactory() (*ChaincodeCmdFactory, error) {
signer, err := common.GetDefaultSigner()
if err != nil {
return nil, err
}
mockResponse := &pb.ProposalResponse{
Response: &pb.Response{Status: 200},
Endorsement: &pb.Endorsement{},
}
mockEndorserClient := common.GetMockEndorserClient(mockResponse, nil)
mockBroadcastClient := common.GetMockBroadcastClient(nil)
mockCF := &ChaincodeCmdFactory{
EndorserClient: mockEndorserClient,
Signer: signer,
BroadcastClient: mockBroadcastClient,
}
return mockCF, nil
return getMockChaincodeCmdFactoryWithEnorserResponses(common.MockResponse{
Response: &pb.ProposalResponse{
Response: &pb.Response{Status: 200},
Endorsement: &pb.Endorsement{},
},
})
}

// Returns mock chaincode command factory that is constructed with an endorser
// client that returns an error for proposal request
func getMockChaincodeCmdFactoryWithErr() (*ChaincodeCmdFactory, error) {
return getMockChaincodeCmdFactoryWithEnorserResponses(common.MockResponse{
Error: errors.New("invoke error"),
})
}

func getMockChaincodeCmdFactoryWithEnorserResponses(responses ...common.MockResponse) (*ChaincodeCmdFactory, error) {
signer, err := common.GetDefaultSigner()
if err != nil {
return nil, err
}

errMsg := "invoke error"
mockEndorerClient := common.GetMockEndorserClient(nil, errors.New(errMsg))
mockEndorserClient := common.GetMockMultiEndorserClient(responses...)
mockBroadcastClient := common.GetMockBroadcastClient(nil)

mockCF := &ChaincodeCmdFactory{
EndorserClient: mockEndorerClient,
EndorserClient: mockEndorserClient,
Signer: signer,
BroadcastClient: mockBroadcastClient,
}
Expand Down
103 changes: 103 additions & 0 deletions peer/chaincode/resources.go
Expand Up @@ -12,11 +12,17 @@ import (
"strings"
"time"

"io/ioutil"
"os"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/common/resourcesconfig"
update2 "github.com/hyperledger/fabric/common/tools/configtxlator/update"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/scc/lscc"
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"
Expand Down Expand Up @@ -325,3 +331,100 @@ func getModPolicies(ccGrp *common.ConfigGroup, update ccUpdate) map[string]strin
}
return modPolicies
}

func configBasedLifecycleUpdate(ss *sigSupport, cf *ChaincodeCmdFactory, config *common.Config, sendInit sendInitTransaction) error {
var env *common.Envelope
hash, err := fetchCCID(ss, cf.EndorserClient, chaincodeName, chaincodeVersion)
if err != nil {
return err
}
if policy == "" {
return errors.New("empty policy")
}
pol, err := cauthdsl.FromString(policy)
if err != nil {
return err
}
update := ccUpdate{
policy: pol,
computeDelta: update2.Compute,
ccName: chaincodeName,
oldConfig: config,
version: chaincodeVersion,
endorsement: escc,
validation: vscc,
hash: hash,
chainID: channelID,
SignatureSupport: &sigSupport{cf.Signer},
}
if resourceEnvelopeLoadPath == "" {
// We're making a new config update
env = update.buildCCUpdateEnvelope()
} else {
// We're loading the config update from disk
updateEnv, err := loadEnvelope(resourceEnvelopeLoadPath)
if err != nil {
return err
}
// and appending our signature to it
updateEnv = update.appendSignature(updateEnv)
// and putting it back into an envelope
env = update.updateIntoEnvelope(updateEnv)
}
if resourceEnvelopeSavePath != "" {
return saveEnvelope(env)
}
if err := cf.BroadcastClient.Send(env); err != nil {
return err
}
return sendInit()
}

func loadEnvelope(file string) (*common.ConfigUpdateEnvelope, error) {
data, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
env := &common.Envelope{}
if err := proto.Unmarshal(data, env); err != nil {
return nil, err
}
payload := &common.Payload{}
if err := proto.Unmarshal(env.Payload, payload); err != nil {
return nil, err
}
update := &common.ConfigUpdateEnvelope{}
if err := proto.Unmarshal(payload.Data, update); err != nil {
return nil, err
}
return update, nil
}

func saveEnvelope(env *common.Envelope) error {
f, err := os.Create(resourceEnvelopeSavePath)
if err != nil {
return errors.Errorf("failed saving resource envelope to file %s: %v", resourceEnvelopeSavePath, err)
}
if _, err := f.Write(utils.MarshalOrPanic(env)); err != nil {
return errors.Errorf("failed saving resource envelope to file %s: %v", resourceEnvelopeSavePath, err)
}
fmt.Printf(`Saved config update envelope to %s.
You can now either:
1) Append your signature using --resourceEnvelopeSave along with --resourceEnvelopeLoad
2) Submit a transaction using only --resourceEnvelopeLoad
`, f.Name())
return nil
}

type sigSupport struct {
msp.SigningIdentity
}

// NewSignatureHeader creates a new signature header
func (s *sigSupport) NewSignatureHeader() (*common.SignatureHeader, error) {
sID, err := s.Serialize()
if err != nil {
return nil, err
}
return utils.MakeSignatureHeader(sID, utils.CreateNonceOrPanic()), nil
}

0 comments on commit 6b636f9

Please sign in to comment.