diff --git a/common/tools/configtxgen/encoder/encoder.go b/common/tools/configtxgen/encoder/encoder.go index bd6712e9a83..3ff9111fcde 100644 --- a/common/tools/configtxgen/encoder/encoder.go +++ b/common/tools/configtxgen/encoder/encoder.go @@ -18,6 +18,7 @@ import ( pb "github.com/hyperledger/fabric/protos/peer" "github.com/hyperledger/fabric/protos/utils" + "github.com/golang/protobuf/proto" "github.com/pkg/errors" ) @@ -274,6 +275,72 @@ func NewConsortiumGroup(conf *genesisconfig.Consortium) (*cb.ConfigGroup, error) return consortiumGroup, nil } +// NewChannelCreateConfigUpdate generates a ConfigUpdate which can be sent to the orderer to create a new channel. Optionally, the channel group of the +// ordering system channel may be passed in, and the resulting ConfigUpdate will extract the appropriate versions from this file. +func NewChannelCreateConfigUpdate(channelID, consortiumName string, orgs []string, orderingSystemChannelGroup *cb.ConfigGroup) (*cb.ConfigUpdate, error) { + var applicationGroup *cb.ConfigGroup + channelGroupVersion := uint64(0) + + if orderingSystemChannelGroup != nil { + // In the case that a ordering system channel definition was provided, pull the appropriate versions + if orderingSystemChannelGroup.Groups == nil { + return nil, errors.New("missing all channel groups") + } + + consortiums, ok := orderingSystemChannelGroup.Groups[channelconfig.ConsortiumsGroupKey] + if !ok { + return nil, errors.New("bad consortiums group") + } + + consortium, ok := consortiums.Groups[consortiumName] + if !ok || (len(orgs) > 0 && consortium.Groups == nil) { + return nil, errors.Errorf("bad consortium: %s", consortiumName) + } + + applicationGroup = cb.NewConfigGroup() + for _, org := range orgs { + orgGroup, ok := consortium.Groups[org] + if !ok { + return nil, errors.Errorf("missing organization: %s", org) + } + applicationGroup.Groups[org] = &cb.ConfigGroup{Version: orgGroup.Version} + } + + channelGroupVersion = orderingSystemChannelGroup.Version + } else { + // Otherwise assume the orgs have not been modified + applicationGroup = cb.NewConfigGroup() + for _, org := range orgs { + applicationGroup.Groups[org] = &cb.ConfigGroup{} + } + } + + rSet := cb.NewConfigGroup() + rSet.Version = channelGroupVersion + + // add the consortium name to the rSet + + addValue(rSet, channelconfig.ConsortiumValue(consortiumName), "") // TODO, this emulates the old behavior, but is it desirable? + + // create the new channel's application group + + rSet.Groups[channelconfig.ApplicationGroupKey] = applicationGroup + + wSet := proto.Clone(rSet).(*cb.ConfigGroup) + + applicationGroup = wSet.Groups[channelconfig.ApplicationGroupKey] + applicationGroup.Version = 1 + applicationGroup.Policies = make(map[string]*cb.ConfigPolicy) + addImplicitMetaPolicyDefaults(applicationGroup) + applicationGroup.ModPolicy = channelconfig.AdminsPolicyKey + + return &cb.ConfigUpdate{ + ChannelId: channelID, + ReadSet: rSet, + WriteSet: wSet, + }, nil +} + // Bootstrapper is a wrapper around NewChannelConfigGroup which can produce genesis blocks type Bootstrapper struct { channelGroup *cb.ConfigGroup diff --git a/common/tools/configtxgen/encoder/encoder_test.go b/common/tools/configtxgen/encoder/encoder_test.go index f28c455765b..9f4be94c106 100644 --- a/common/tools/configtxgen/encoder/encoder_test.go +++ b/common/tools/configtxgen/encoder/encoder_test.go @@ -10,10 +10,12 @@ import ( "testing" "github.com/hyperledger/fabric/common/channelconfig" + "github.com/hyperledger/fabric/common/configtx" "github.com/hyperledger/fabric/common/flogging" genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig" cb "github.com/hyperledger/fabric/protos/common" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" ) @@ -61,3 +63,80 @@ func TestConfigParsing(t *testing.T) { }) } } + +func TestGoodChannelCreateConfigUpdate(t *testing.T) { + config := genesisconfig.Load(genesisconfig.SampleDevModeSoloProfile) + group, err := NewChannelGroup(config) + assert.NoError(t, err) + assert.NotNil(t, group) + + configUpdate, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, group) + assert.NoError(t, err) + assert.NotNil(t, configUpdate) + + defaultConfigUpdate, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, nil) + assert.NoError(t, err) + assert.NotNil(t, configUpdate) + + assert.True(t, proto.Equal(configUpdate, defaultConfigUpdate), "the config used has had no updates, so should equal default") +} + +func TestNegativeChannelCreateConfigUpdate(t *testing.T) { + config := genesisconfig.Load(genesisconfig.SampleDevModeSoloProfile) + group, err := NewChannelGroup(config) + assert.NoError(t, err) + assert.NotNil(t, group) + + t.Run("NoGroups", func(t *testing.T) { + channelGroup := proto.Clone(group).(*cb.ConfigGroup) + channelGroup.Groups = nil + _, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, channelGroup) + assert.Error(t, err) + assert.Regexp(t, "missing all channel groups", err.Error()) + }) + + t.Run("NoConsortiumsGroup", func(t *testing.T) { + channelGroup := proto.Clone(group).(*cb.ConfigGroup) + delete(channelGroup.Groups, channelconfig.ConsortiumsGroupKey) + _, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, channelGroup) + assert.Error(t, err) + assert.Regexp(t, "bad consortiums group", err.Error()) + }) + + t.Run("NoConsortiums", func(t *testing.T) { + channelGroup := proto.Clone(group).(*cb.ConfigGroup) + delete(channelGroup.Groups[channelconfig.ConsortiumsGroupKey].Groups, genesisconfig.SampleConsortiumName) + _, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, channelGroup) + assert.Error(t, err) + assert.Regexp(t, "bad consortium:", err.Error()) + }) + + t.Run("MissingOrg", func(t *testing.T) { + channelGroup := proto.Clone(group).(*cb.ConfigGroup) + _, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName + ".wrong"}, channelGroup) + assert.Error(t, err) + assert.Regexp(t, "missing organization:", err.Error()) + }) +} + +// This is a temporary test to make sure that the newly implement channel creation method properly replicates +// the old behavior +func TestCompatability(t *testing.T) { + config := genesisconfig.Load(genesisconfig.SampleDevModeSoloProfile) + group, err := NewChannelGroup(config) + assert.NoError(t, err) + assert.NotNil(t, group) + + channelID := "channel.id" + orgs := []string{genesisconfig.SampleOrgName} + configUpdate, err := NewChannelCreateConfigUpdate(channelID, genesisconfig.SampleConsortiumName, orgs, group) + assert.NoError(t, err) + assert.NotNil(t, configUpdate) + + template := channelconfig.NewChainCreationTemplate(genesisconfig.SampleConsortiumName, orgs) + configEnv, err := template.Envelope(channelID) + assert.NoError(t, err) + oldUpdate := configtx.UnmarshalConfigUpdateOrPanic(configEnv.ConfigUpdate) + oldUpdate.IsolatedData = nil + assert.True(t, proto.Equal(oldUpdate, configUpdate)) +}