Skip to content

Commit

Permalink
FABG-909 - Multi-org channel config update
Browse files Browse the repository at this point in the history
Replaced UpdateChannelConfig with CalculateConfigUpdate
function which calculates channel update Tx based on the
desired channel config and current channel config.

Deprecated SaveChannelRequest.SigningIdentities.

Added integration test to demonstrate multi-org signing
workflow when changing channel configuration using the
new function.

Signed-off-by: Aleksandar Likic <aleksandar.likic@securekey.com>
Change-Id: I582f521e36a3ae060032ff71976370a5046ce83a
  • Loading branch information
Aleksandar Likic committed Sep 17, 2019
1 parent 3b103fa commit fc08b96
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 215 deletions.
39 changes: 0 additions & 39 deletions pkg/client/resmgmt/example_test.go
Expand Up @@ -12,8 +12,6 @@ import (
"path/filepath"
"time"

"github.com/hyperledger/fabric-sdk-go/pkg/util/test"

"github.com/golang/protobuf/proto"

"github.com/hyperledger/fabric-protos-go/common"
Expand Down Expand Up @@ -249,43 +247,6 @@ func ExampleClient_SaveChannel_withOrdererEndpoint() {

}

func ExampleClient_UpdateChannelConfig_success() {

c, err := New(mockClientProvider())
if err != nil {
fmt.Printf("failed to create client: %s\n", err)
}

block, err := c.QueryConfigBlockFromOrderer("mychannel", WithOrdererEndpoint("example.com"))
if err != nil {
fmt.Printf("QueryConfigBlockFromOrderer returned error: %s\n", err)
}
channelConfig, err := resource.ExtractConfigFromBlock(block)
if err != nil {
fmt.Println("extractConfigFromBlock failed")
}

// Modify channel configuration
_, err = test.ModifyMaxMessageCount(channelConfig)
if err != nil {
fmt.Printf("error modifying channel configuration: %s\n", err)
}

resp, err := c.UpdateChannelConfig(UpdateChannelConfigRequest{ChannelID: "mychannel", ChannelConfig: channelConfig}, WithOrdererEndpoint("example.com"))
if err != nil {
fmt.Printf("failed to update channel config: %s\n", err)
}

if resp.TransactionID == "" {
fmt.Println("Failed to save channel")
}

fmt.Println("Updated channel config")

// Output: Updated channel config

}

func ExampleClient_JoinChannel() {

c, err := New(mockClientProvider())
Expand Down
103 changes: 16 additions & 87 deletions pkg/client/resmgmt/resmgmt.go
Expand Up @@ -112,28 +112,18 @@ type requestOptions struct {
//SaveChannelRequest holds parameters for save channel request
type SaveChannelRequest struct {
ChannelID string
ChannelConfig io.Reader // ChannelConfig data source
ChannelConfigPath string // Convenience option to use the named file as ChannelConfig reader
SigningIdentities []msp.SigningIdentity // Users that sign channel configuration
ChannelConfig io.Reader // ChannelConfig data source
ChannelConfigPath string // Convenience option to use the named file as ChannelConfig reader
// Users that sign channel configuration
// deprecated - one entity shouldn't have access to another entities' keys to sign on their behalf
SigningIdentities []msp.SigningIdentity
}

// SaveChannelResponse contains response parameters for save channel
type SaveChannelResponse struct {
TransactionID fab.TransactionID
}

// UpdateChannelConfigRequest holds parameters for update channel config request.
type UpdateChannelConfigRequest struct {
ChannelID string
ChannelConfig *common.Config // Desired channel config.
SigningIdentities []msp.SigningIdentity // Users that sign channel configuration
}

// UpdateChannelConfigResponse contains response parameters for update channel config
type UpdateChannelConfigResponse struct {
TransactionID fab.TransactionID
}

//RequestOption func for each Opts argument
type RequestOption func(ctx context.Client, opts *requestOptions) error

Expand Down Expand Up @@ -971,54 +961,6 @@ func (rc *Client) SaveChannel(req SaveChannelRequest, options ...RequestOption)
return SaveChannelResponse{TransactionID: txID}, nil
}

// UpdateChannelConfig updates channel configuration.
// Parameters:
// req holds info about mandatory channel name and configuration
// options holds optional request options
// if options have signatures (WithConfigSignatures() or 1 or more WithConfigSignature() calls), then UpdateChannelConfig will
// use these signatures instead of creating ones for the SigningIdentities found in req.
// Make sure that req.ChannelConfig has the channel config matching these signatures.
//
// Returns:
// update channel config response with transaction ID
func (rc *Client) UpdateChannelConfig(req UpdateChannelConfigRequest, options ...RequestOption) (UpdateChannelConfigResponse, error) {

opts, err := rc.prepareRequestOpts(options...)
if err != nil {
return UpdateChannelConfigResponse{}, err
}

err = rc.validateUpdateChannelConfigRequest(req)
if err != nil {
return UpdateChannelConfigResponse{}, err
}

logger.Debugf("updating channel config: %s", req.ChannelID)

orderer, err := rc.requestOrderer(&opts, req.ChannelID)
if err != nil {
return UpdateChannelConfigResponse{}, errors.WithMessage(err, "failed to find orderer for request")
}

chConfig, err := rc.calculateConfigUpdate(req, orderer)
if err != nil {
return UpdateChannelConfigResponse{}, errors.WithMessage(err, "prepare channel ConfigTx failed")
}

txID, err := rc.signAndSubmitChannelConfigTx(
req.ChannelID,
req.SigningIdentities,
opts,
chConfig,
orderer,
)
if err != nil {
return UpdateChannelConfigResponse{}, errors.WithMessage(err, "update channel config failed")
}

return UpdateChannelConfigResponse{TransactionID: txID}, nil
}

func (rc *Client) signAndSubmitChannelConfigTx(channelID string, signingIdentities []msp.SigningIdentity, opts requestOptions, chConfigTx []byte, orderer fab.Orderer) (fab.TransactionID, error) {
var configSignatures []*common.ConfigSignature
var err error
Expand Down Expand Up @@ -1048,28 +990,23 @@ func (rc *Client) signAndSubmitChannelConfigTx(channelID string, signingIdentiti
return txID, nil
}

func (rc *Client) calculateConfigUpdate(req UpdateChannelConfigRequest, orderer fab.Orderer) ([]byte, error) {
block, err := rc.QueryConfigBlockFromOrderer(req.ChannelID, WithOrderer(orderer))
if err != nil {
return nil, errors.WithMessage(err, "retrieving current channel config failed")
}
currentConfig, err := resource.ExtractConfigFromBlock(block)
if err != nil {
return nil, errors.WithMessage(err, "extracting config from the latest block failed")
// CalculateConfigUpdate calculates channel config update based on the difference between provided
// current channel config and proposed new channel config.
func CalculateConfigUpdate(channelID string, currentConfig, newConfig *common.Config) (*common.ConfigUpdate, error) {

if channelID == "" || currentConfig == nil || newConfig == nil {
return nil, errors.New("must provide channel ID and current and new channel config")
}
if currentConfig.Sequence != req.ChannelConfig.Sequence {

if currentConfig.Sequence != newConfig.Sequence {
return nil, errors.New("channel config sequence mismatch")
}
configUpdate, err := update.Compute(currentConfig, req.ChannelConfig)
configUpdate, err := update.Compute(currentConfig, newConfig)
if err != nil {
return nil, errors.WithMessage(err, "config update computation failed")
}
configUpdate.ChannelId = req.ChannelID
configUpdateBytes, err := proto.Marshal(configUpdate)
if err != nil {
return nil, errors.WithMessage(err, "marshalling config update failed")
}
return configUpdateBytes, nil
configUpdate.ChannelId = channelID
return configUpdate, nil
}

func (rc *Client) validateSaveChannelRequest(req SaveChannelRequest) error {
Expand All @@ -1080,14 +1017,6 @@ func (rc *Client) validateSaveChannelRequest(req SaveChannelRequest) error {
return nil
}

func (rc *Client) validateUpdateChannelConfigRequest(req UpdateChannelConfigRequest) error {

if req.ChannelID == "" || req.ChannelConfig == nil {
return errors.New("must provide channel ID and channel config")
}
return nil
}

func (rc *Client) getConfigSignatures(signingIdentities []msp.SigningIdentity, chConfig []byte) ([]*common.ConfigSignature, error) {
// Signing user has to belong to one of configured channel organisations
// In case that order org is one of channel orgs we can use context user
Expand Down
53 changes: 15 additions & 38 deletions pkg/client/resmgmt/resmgmt_test.go
Expand Up @@ -19,6 +19,8 @@ import (
"testing"
"time"

"github.com/hyperledger/fabric-protos-go/orderer"

"github.com/hyperledger/fabric-sdk-go/pkg/util/test"

"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
Expand Down Expand Up @@ -1075,40 +1077,16 @@ func getNetworkConfigWithoutOrderer(t *testing.T) fab.EndpointConfig {
return config
}

func TestUpdateChannelConfigSuccess(t *testing.T) {
ctx := setupTestContext("test", "Org1MSP")
func TestCalculateConfigUpdate(t *testing.T) {

// Create mock orderer with simple mock block
orderer := fcmocks.NewMockOrderer("", nil)
//defer orderer.CloseQueue()

setupCustomOrderer(ctx, orderer)
cc := setupResMgmtClient(t, ctx)

// Test valid Save Channel request using config block (success)
// Get the original configuration
originalConfiglBlockBytes, err := ioutil.ReadFile(filepath.Join("testdata", "config.block"))
assert.Nil(t, err, "opening config.block file failed")
originalConfigBlock := &common.Block{}
assert.Nil(t, proto.Unmarshal(originalConfiglBlockBytes, originalConfigBlock), "unmarshalling originalConfigBlock failed")
originalConfig, err := resource.ExtractConfigFromBlock(originalConfigBlock)
assert.Nil(t, err, "extractConfigFromBlock failed")

orderer.EnqueueForSendDeliver(
// The first call to orderer returns the very last block
// For testing, we can put here any valid block.
originalConfigBlock,
common.Status_SUCCESS,
)
orderer.EnqueueForSendDeliver(
// The next call returns the last configuration block,
// which is the input to config update tx calculation
originalConfigBlock,
common.Status_SUCCESS,
)
resp, err := cc.UpdateChannelConfig(UpdateChannelConfigRequest{ChannelID: "mychannel", ChannelConfig: originalConfig}, WithOrderer(orderer))
assert.NotNil(t, err, "Should have failed for unchanged configuration")
assert.Contains(t, err.Error(), "no differences detected between original and updated config")

// Prepare new configuration
modifiedConfigBytes, err := proto.Marshal(originalConfig)
assert.Nil(t, err, "error marshalling originalConfig")
Expand All @@ -1118,19 +1096,18 @@ func TestUpdateChannelConfigSuccess(t *testing.T) {
assert.Nil(t, err, "error modifying config")
assert.Nil(t, test.VerifyMaxMessageCount(modifiedConfig, newMaxMessageCount), "error verifying modified config")

orderer.EnqueueForSendDeliver(
originalConfigBlock,
common.Status_SUCCESS,
)
orderer.EnqueueForSendDeliver(
originalConfigBlock,
common.Status_SUCCESS,
)
resp, err = cc.UpdateChannelConfig(UpdateChannelConfigRequest{ChannelID: "mychannel", ChannelConfig: modifiedConfig}, WithOrderer(orderer))
assert.Nil(t, err, "error should be nil")
assert.NotEmpty(t, resp.TransactionID, "transaction ID should be populated")
channelID := "mychannel"

configUpdate, err := CalculateConfigUpdate(channelID, originalConfig, modifiedConfig)
assert.NoError(t, err, "calculating config update failed")
assert.NotNil(t, configUpdate, "calculated config update is nil")
assert.Equal(t, channelID, configUpdate.ChannelId, "channel ID mismatch")

updatedBatchSizeBytes := configUpdate.WriteSet.Groups["Orderer"].Values["BatchSize"].Value
batchSize := &orderer.BatchSize{}
assert.Nil(t, proto.Unmarshal(updatedBatchSizeBytes, batchSize), "unmarshalling BatchSize failed")
assert.Equal(t, newMaxMessageCount, batchSize.MaxMessageCount, "MaxMessageCount mismatch")

orderer.CloseQueue()
}

func TestSaveChannelSuccess(t *testing.T) {
Expand Down
47 changes: 47 additions & 0 deletions pkg/util/test/util.go
Expand Up @@ -9,11 +9,58 @@ package test
import (
"fmt"

"github.com/hyperledger/fabric-protos-go/peer"

"github.com/pkg/errors"

"github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/protoutil"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric-protos-go/orderer"
)

// AddACL adds an ACL config value to channel config
func AddACL(config *common.Config, policyName, policy string) error {

aclsConfigValue, ok := config.ChannelGroup.Groups["Application"].Values["ACLs"]
if !ok {
return errors.New("ACL missing from Application config")
}
acls := &peer.ACLs{}
err := proto.Unmarshal(aclsConfigValue.Value, acls)
if err != nil {
return err
}
acls.Acls[policyName] = &peer.APIResource{PolicyRef: policy}
aclsConfigValue.Value = protoutil.MarshalOrPanic(acls)

return nil
}

// VerifyACL verifies an ACL config value
func VerifyACL(config *common.Config, expectedPolicyName, expectedPolicy string) error {

aclsConfigValue, ok := config.ChannelGroup.Groups["Application"].Values["ACLs"]
if !ok {
return errors.New("ACL missing from Application config")
}
acls := &peer.ACLs{}
err := proto.Unmarshal(aclsConfigValue.Value, acls)
if err != nil {
return err
}
resource, ok := acls.Acls[expectedPolicyName]
if !ok {
return errors.Errorf("missing expected policy name: %s", expectedPolicyName)
}
if resource.PolicyRef != expectedPolicy {
return errors.Errorf("unexpected policy ref: %s, expected: %s", resource.PolicyRef, expectedPolicy)
}

return nil
}

// ModifyMaxMessageCount increments the orderer's BatchSize.MaxMessageCount in a channel config
func ModifyMaxMessageCount(config *common.Config) (uint32, error) {

Expand Down

0 comments on commit fc08b96

Please sign in to comment.