diff --git a/peer/channel/channel.go b/peer/channel/channel.go index 003e8474654..277bb34a000 100644 --- a/peer/channel/channel.go +++ b/peer/channel/channel.go @@ -73,6 +73,7 @@ func Cmd(cf *ChannelCmdFactory) *cobra.Command { channelCmd.AddCommand(joinCmd(cf)) channelCmd.AddCommand(listCmd(cf)) channelCmd.AddCommand(updateCmd(cf)) + channelCmd.AddCommand(signconfigtxCmd(cf)) return channelCmd } diff --git a/peer/channel/create.go b/peer/channel/create.go index d6157cb5d36..2b37f1d510f 100644 --- a/peer/channel/create.go +++ b/peer/channel/create.go @@ -116,6 +116,12 @@ func sanityCheckAndSignConfigTx(envConfigUpdate *cb.Envelope) (*cb.Envelope, err return nil, InvalidCreateTx("empty channel id") } + // Specifying the chainID on the CLI is usually redundant, as a hack, set it + // here if it has not been set explicitly + if chainID == "" { + chainID = ch.ChannelId + } + if ch.ChannelId != chainID { return nil, InvalidCreateTx(fmt.Sprintf("mismatched channel ID %s != %s", ch.ChannelId, chainID)) } diff --git a/peer/channel/signconfigtx.go b/peer/channel/signconfigtx.go new file mode 100644 index 00000000000..e9ded84247b --- /dev/null +++ b/peer/channel/signconfigtx.go @@ -0,0 +1,65 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channel + +import ( + "io/ioutil" + + "github.com/hyperledger/fabric/protos/utils" + + "github.com/spf13/cobra" +) + +func signconfigtxCmd(cf *ChannelCmdFactory) *cobra.Command { + signconfigtxCmd := &cobra.Command{ + Use: "signconfigtx", + Short: "Signs a configtx update.", + Long: "Signs the supplied configtx update file in place on the filesystem. Requires '-f'.", + RunE: func(cmd *cobra.Command, args []string) error { + return sign(cmd, args, cf) + }, + } + flagList := []string{ + "file", + } + attachFlags(signconfigtxCmd, flagList) + + return signconfigtxCmd +} + +func sign(cmd *cobra.Command, args []string, cf *ChannelCmdFactory) error { + if channelTxFile == "" { + return InvalidCreateTx("No configtx file name supplied") + } + + var err error + if cf == nil { + cf, err = InitCmdFactory(EndorserNotRequired, OrdererNotRequired) + if err != nil { + return err + } + } + + fileData, err := ioutil.ReadFile(channelTxFile) + if err != nil { + return ConfigTxFileNotFound(err.Error()) + } + + ctxEnv, err := utils.UnmarshalEnvelope(fileData) + if err != nil { + return err + } + + sCtxEnv, err := sanityCheckAndSignConfigTx(ctxEnv) + if err != nil { + return err + } + + sCtxEnvData := utils.MarshalOrPanic(sCtxEnv) + + return ioutil.WriteFile(channelTxFile, sCtxEnvData, 0660) +} diff --git a/peer/channel/signconfigtx_test.go b/peer/channel/signconfigtx_test.go new file mode 100644 index 00000000000..fb044497464 --- /dev/null +++ b/peer/channel/signconfigtx_test.go @@ -0,0 +1,98 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channel + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/hyperledger/fabric/peer/common" + cb "github.com/hyperledger/fabric/protos/common" + + "github.com/stretchr/testify/assert" +) + +func TestSignConfigtx(t *testing.T) { + InitMSP() + resetFlags() + + dir, err := ioutil.TempDir("/tmp", "signconfigtxtest-") + if err != nil { + t.Fatalf("couldn't create temp dir") + } + defer os.RemoveAll(dir) // clean up + + configtxFile := filepath.Join(dir, mockChannel) + if _, err = createTxFile(configtxFile, cb.HeaderType_CONFIG_UPDATE, mockChannel); err != nil { + t.Fatalf("couldn't create tx file") + } + + signer, err := common.GetDefaultSigner() + if err != nil { + t.Fatalf("Get default signer error: %v", err) + } + + mockCF := &ChannelCmdFactory{ + Signer: signer, + } + + cmd := signconfigtxCmd(mockCF) + + AddFlags(cmd) + + args := []string{"-f", configtxFile} + cmd.SetArgs(args) + + assert.NoError(t, cmd.Execute()) +} + +func TestSignConfigtxMissingConfigTxFlag(t *testing.T) { + InitMSP() + resetFlags() + + signer, err := common.GetDefaultSigner() + if err != nil { + t.Fatalf("Get default signer error: %v", err) + } + + mockCF := &ChannelCmdFactory{ + Signer: signer, + } + + cmd := signconfigtxCmd(mockCF) + + AddFlags(cmd) + + cmd.SetArgs([]string{}) + + assert.Error(t, cmd.Execute()) +} + +func TestSignConfigtxChannelMissingConfigTxFile(t *testing.T) { + InitMSP() + resetFlags() + + signer, err := common.GetDefaultSigner() + if err != nil { + t.Fatalf("Get default signer error: %v", err) + } + + mockCF := &ChannelCmdFactory{ + Signer: signer, + } + + cmd := signconfigtxCmd(mockCF) + + AddFlags(cmd) + + args := []string{"-f", "Non-existant"} + cmd.SetArgs(args) + + assert.Error(t, cmd.Execute()) +}