Skip to content

Commit

Permalink
[FAB-6234] configtxgen emit resource defaults
Browse files Browse the repository at this point in the history
This changeset at its core, simply enables embedding a default resource
tree into the output of channel creation transactions which are created
with v1.1 application capabilities.

Unit tests have been added. Additionally, the new channel creation tx
behavior can be confirmed by issuing:

 configtxgen -profile SampleSingleMSPChannelV1_1 \
  -outputCreateChannelTx foo.tx -inspectChannelCreateTx foo.tx

and observing the resources tree in the JSON output.

Because the utility function for creating the channel creation
transaction is used widely throughout the tests, and the function
parameters needed to change significantly, there is a fair amount of
churn in the tests, but none in production code.

Change-Id: Id719a1ae3f309a0a189f566dc0fef409564b519e
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick authored and yacovm committed Dec 6, 2017
1 parent 268e6bc commit 500d3de
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 143 deletions.
158 changes: 95 additions & 63 deletions common/tools/configtxgen/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ package encoder
import (
"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/crypto"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/common/genesis"
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/common/resourcesconfig"
genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig"
"github.com/hyperledger/fabric/common/tools/configtxlator/update"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/msp"
cb "github.com/hyperledger/fabric/protos/common"
Expand Down Expand Up @@ -281,12 +284,30 @@ func NewConsortiumGroup(conf *genesisconfig.Consortium) (*cb.ConfigGroup, error)

// 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)
func NewChannelCreateConfigUpdate(channelID string, orderingSystemChannelGroup *cb.ConfigGroup, conf *genesisconfig.Profile) (*cb.ConfigUpdate, error) {
if conf.Application == nil {
return nil, errors.New("cannot define a new channel with no Application section")
}

if conf.Consortium == "" {
return nil, errors.New("cannot define a new channel with no Consortium value")
}

// Otherwise, parse only the application section, and encapsulate it inside a channel group
ag, err := NewApplicationGroup(conf.Application)
if err != nil {
return nil, errors.Wrapf(err, "could not turn channel application profile into application group")
}

agc, err := channelconfig.NewApplicationConfig(ag, channelconfig.NewMSPConfigHandler(msp.MSPv1_1))
if err != nil {
return nil, errors.Wrapf(err, "could not parse application to application group")
}

var template, newChannelGroup *cb.ConfigGroup

if orderingSystemChannelGroup != nil {
// In the case that a ordering system channel definition was provided, pull the appropriate versions
// In the case that a ordering system channel definition was provided, use it to compute the update
if orderingSystemChannelGroup.Groups == nil {
return nil, errors.New("missing all channel groups")
}
Expand All @@ -296,58 +317,86 @@ func NewChannelCreateConfigUpdate(channelID, consortiumName string, orgs []strin
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)
consortium, ok := consortiums.Groups[conf.Consortium]
if !ok {
return nil, errors.Errorf("bad consortium: %s", conf.Consortium)
}

applicationGroup = cb.NewConfigGroup()
for _, org := range orgs {
orgGroup, ok := consortium.Groups[org]
if !ok {
return nil, errors.Errorf("missing organization: %s", org)
template = proto.Clone(orderingSystemChannelGroup).(*cb.ConfigGroup)
template.Groups[channelconfig.ApplicationGroupKey] = proto.Clone(consortium).(*cb.ConfigGroup)
// This is a bit of a hack. If the channel config specifies all consortium members, then it does not look
// like a modification. The below adds a fake org with an illegal name which cannot actually exist, which
// will always appear to be deleted, triggering the correct update computation.
template.Groups[channelconfig.ApplicationGroupKey].Groups["*IllegalKey*!"] = &cb.ConfigGroup{}
delete(template.Groups, channelconfig.ConsortiumsGroupKey)

newChannelGroup = proto.Clone(orderingSystemChannelGroup).(*cb.ConfigGroup)
delete(newChannelGroup.Groups, channelconfig.ConsortiumsGroupKey)
newChannelGroup.Groups[channelconfig.ApplicationGroupKey].Values = ag.Values
newChannelGroup.Groups[channelconfig.ApplicationGroupKey].Policies = ag.Policies

for orgName, org := range template.Groups[channelconfig.ApplicationGroupKey].Groups {
if _, ok := ag.Groups[orgName]; ok {
newChannelGroup.Groups[channelconfig.ApplicationGroupKey].Groups[orgName] = 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{}
newChannelGroup = &cb.ConfigGroup{
Groups: map[string]*cb.ConfigGroup{
channelconfig.ApplicationGroupKey: ag,
},
}
}

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
// Otherwise assume the orgs have not been modified
template = proto.Clone(newChannelGroup).(*cb.ConfigGroup)
template.Groups[channelconfig.ApplicationGroupKey].Values = nil
template.Groups[channelconfig.ApplicationGroupKey].Policies = nil
}

rSet.Groups[channelconfig.ApplicationGroupKey] = applicationGroup
updt, err := update.Compute(&cb.Config{ChannelGroup: template}, &cb.Config{ChannelGroup: newChannelGroup})
if err != nil {
return nil, errors.Wrapf(err, "could not compute update")
}

wSet := proto.Clone(rSet).(*cb.ConfigGroup)
// Add the consortium name to create the channel for into the write set as required.
updt.ChannelId = channelID
updt.ReadSet.Values[channelconfig.ConsortiumKey] = &cb.ConfigValue{Version: 0}
updt.WriteSet.Values[channelconfig.ConsortiumKey] = &cb.ConfigValue{
Version: 0,
Value: utils.MarshalOrPanic(&cb.Consortium{
Name: conf.Consortium,
}),
}

applicationGroup = wSet.Groups[channelconfig.ApplicationGroupKey]
applicationGroup.Version = 1
applicationGroup.Policies = make(map[string]*cb.ConfigPolicy)
addImplicitMetaPolicyDefaults(applicationGroup)
applicationGroup.ModPolicy = channelconfig.AdminsPolicyKey
// If this channel uses the new lifecycle config, specify the seed data
if agc.Capabilities().LifecycleViaConfig() {
updt.IsolatedData = map[string][]byte{
pb.RSCCSeedDataKey: utils.MarshalOrPanic(&cb.Config{
Type: int32(cb.ConfigType_RESOURCE),
ChannelGroup: &cb.ConfigGroup{
Groups: map[string]*cb.ConfigGroup{
resourcesconfig.ChaincodesGroupKey: &cb.ConfigGroup{
ModPolicy: policies.ChannelApplicationAdmins,
},
resourcesconfig.PeerPoliciesGroupKey: &cb.ConfigGroup{
ModPolicy: policies.ChannelApplicationAdmins,
},
resourcesconfig.APIsGroupKey: &cb.ConfigGroup{
ModPolicy: policies.ChannelApplicationAdmins,
},
},
ModPolicy: policies.ChannelApplicationAdmins,
},
}),
}
}

return &cb.ConfigUpdate{
ChannelId: channelID,
ReadSet: rSet,
WriteSet: wSet,
}, nil
return updt, nil
}

// MakeChannelCreationTransaction is a handy utility function for creating transactions for channel creation
func MakeChannelCreationTransaction(channelID string, consortium string, signer msp.SigningIdentity, orderingSystemChannelConfigGroup *cb.ConfigGroup, orgs ...string) (*cb.Envelope, error) {
newChannelConfigUpdate, err := NewChannelCreateConfigUpdate(channelID, consortium, orgs, orderingSystemChannelConfigGroup)
func MakeChannelCreationTransaction(channelID string, signer crypto.LocalSigner, orderingSystemChannelConfigGroup *cb.ConfigGroup, conf *genesisconfig.Profile) (*cb.Envelope, error) {
newChannelConfigUpdate, err := NewChannelCreateConfigUpdate(channelID, orderingSystemChannelConfigGroup, conf)
if err != nil {
return nil, errors.Wrap(err, "config update generation failure")
}
Expand All @@ -356,41 +405,24 @@ func MakeChannelCreationTransaction(channelID string, consortium string, signer
ConfigUpdate: utils.MarshalOrPanic(newChannelConfigUpdate),
}

payloadSignatureHeader := &cb.SignatureHeader{}
if signer != nil {
sSigner, err := signer.Serialize()
sigHeader, err := signer.NewSignatureHeader()
if err != nil {
return nil, errors.Wrap(err, "serialization of identity failed")
return nil, errors.Wrap(err, "creating signature header failed")
}

newConfigUpdateEnv.Signatures = []*cb.ConfigSignature{&cb.ConfigSignature{
SignatureHeader: utils.MarshalOrPanic(utils.MakeSignatureHeader(sSigner, utils.CreateNonceOrPanic())),
SignatureHeader: utils.MarshalOrPanic(sigHeader),
}}

newConfigUpdateEnv.Signatures[0].Signature, err = signer.Sign(util.ConcatenateBytes(newConfigUpdateEnv.Signatures[0].SignatureHeader, newConfigUpdateEnv.ConfigUpdate))
if err != nil {
return nil, errors.Wrap(err, "signature failure over config update")
}

payloadSignatureHeader = utils.MakeSignatureHeader(sSigner, utils.CreateNonceOrPanic())
}

payloadChannelHeader := utils.MakeChannelHeader(cb.HeaderType_CONFIG_UPDATE, msgVersion, channelID, epoch)
utils.SetTxID(payloadChannelHeader, payloadSignatureHeader)
payloadHeader := utils.MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader)
payload := &cb.Payload{Header: payloadHeader, Data: utils.MarshalOrPanic(newConfigUpdateEnv)}
paylBytes := utils.MarshalOrPanic(payload)

var sig []byte
if signer != nil {
// sign the payload
sig, err = signer.Sign(paylBytes)
if err != nil {
return nil, errors.Wrap(err, "signature failure over config update envelope")
}
}

return &cb.Envelope{Payload: paylBytes, Signature: sig}, nil
return utils.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, signer, newConfigUpdateEnv, msgVersion, epoch)
}

// Bootstrapper is a wrapper around NewChannelConfigGroup which can produce genesis blocks
Expand Down
58 changes: 39 additions & 19 deletions common/tools/configtxgen/encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/configtx"
"github.com/hyperledger/fabric/common/flogging"
mmsp "github.com/hyperledger/fabric/common/mocks/msp"
"github.com/hyperledger/fabric/common/localmsp"
genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig"
mspmgmt "github.com/hyperledger/fabric/msp/mgmt"
cb "github.com/hyperledger/fabric/protos/common"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"

"github.com/golang/protobuf/proto"
Expand Down Expand Up @@ -70,66 +72,84 @@ func TestConfigParsing(t *testing.T) {

func TestGoodChannelCreateConfigUpdate(t *testing.T) {
config := genesisconfig.Load(genesisconfig.SampleDevModeSoloProfile)
group, err := NewChannelGroup(config)
systemChannel, err := NewChannelGroup(config)
assert.NoError(t, err)
assert.NotNil(t, group)
assert.NotNil(t, systemChannel)

createConfig := genesisconfig.Load(genesisconfig.SampleSingleMSPChannelProfile)

configUpdate, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, group)
configUpdate, err := NewChannelCreateConfigUpdate("channel.id", nil, createConfig)
assert.NoError(t, err)
assert.NotNil(t, configUpdate)

defaultConfigUpdate, err := NewChannelCreateConfigUpdate("channel.id", genesisconfig.SampleConsortiumName, []string{genesisconfig.SampleOrgName}, nil)
defaultConfigUpdate, err := NewChannelCreateConfigUpdate("channel.id", systemChannel, createConfig)
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 TestChannelCreateWithResources(t *testing.T) {
t.Run("AtV1.0", func(t *testing.T) {
createConfig := genesisconfig.Load(genesisconfig.SampleSingleMSPChannelProfile)

configUpdate, err := NewChannelCreateConfigUpdate("channel.id", nil, createConfig)
assert.NoError(t, err)
assert.NotNil(t, configUpdate)
assert.Nil(t, configUpdate.IsolatedData)
})

t.Run("AtV1.1", func(t *testing.T) {
createConfig := genesisconfig.Load(genesisconfig.SampleSingleMSPChannelV11Profile)

configUpdate, err := NewChannelCreateConfigUpdate("channel.id", nil, createConfig)
assert.NoError(t, err)
assert.NotNil(t, configUpdate)
assert.NotNil(t, configUpdate.IsolatedData)
assert.NotEmpty(t, configUpdate.IsolatedData[pb.RSCCSeedDataKey])
})

}

func TestNegativeChannelCreateConfigUpdate(t *testing.T) {
config := genesisconfig.Load(genesisconfig.SampleDevModeSoloProfile)
channelConfig := genesisconfig.Load(genesisconfig.SampleSingleMSPChannelProfile)
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)
_, err := NewChannelCreateConfigUpdate("channel.id", &cb.ConfigGroup{}, channelConfig)
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)
_, err := NewChannelCreateConfigUpdate("channel.id", channelGroup, channelConfig)
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)
_, err := NewChannelCreateConfigUpdate("channel.id", channelGroup, channelConfig)
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())
})
}

func TestMakeChannelCreationTransactionWithSigner(t *testing.T) {
channelID := "foo"

signer, err := mmsp.NewNoopMsp().GetDefaultSigningIdentity()
assert.NoError(t, err, "Creating noop MSP")
mspmgmt.LoadDevMsp()
signer := localmsp.NewSigner()

cct, err := MakeChannelCreationTransaction(channelID, "test", signer, nil)
cct, err := MakeChannelCreationTransaction(channelID, signer, nil, genesisconfig.Load(genesisconfig.SampleSingleMSPChannelV11Profile))
assert.NoError(t, err, "Making chain creation tx")

assert.NotEmpty(t, cct.Signature, "Should have signature")
Expand All @@ -149,7 +169,7 @@ func TestMakeChannelCreationTransactionWithSigner(t *testing.T) {

func TestMakeChannelCreationTransactionNoSigner(t *testing.T) {
channelID := "foo"
cct, err := MakeChannelCreationTransaction(channelID, "test", nil, nil)
cct, err := MakeChannelCreationTransaction(channelID, nil, nil, genesisconfig.Load(genesisconfig.SampleSingleMSPChannelProfile))
assert.NoError(t, err, "Making chain creation tx")

assert.Empty(t, cct.Signature, "Should have empty signature")
Expand Down
4 changes: 4 additions & 0 deletions common/tools/configtxgen/localconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const (
SampleInsecureSoloProfile = "SampleInsecureSolo"
// SampleDevModeSoloProfile references the sample profile which requires only basic membership for admin privileges and uses solo for ordering.
SampleDevModeSoloProfile = "SampleDevModeSolo"
// SampleDevModeSoloProfileV11 references the sample profile which requires only basic membership for admin privileges and uses solo for ordering, but has v1.1 capabilities enabled
SampleDevModeSoloV11Profile = "SampleDevModeSoloV1_1"
// SampleSingleMSPSoloProfile references the sample profile which includes only the sample MSP and uses solo for ordering.
SampleSingleMSPSoloProfile = "SampleSingleMSPSolo"
// SampleSingleMSPSoloV11Profile references the sample profile which includes only the sample MSP with v1.1 capabilities defined and uses solo for ordering.
Expand All @@ -61,6 +63,8 @@ const (
SampleInsecureKafkaProfile = "SampleInsecureKafka"
// SampleDevModeKafkaProfile references the sample profile which requires only basic membership for admin privileges and uses Kafka for ordering.
SampleDevModeKafkaProfile = "SampleDevModeKafka"
// SampleDevModeKafkaProfileV11 references the sample profile which requires only basic membership for admin privileges and uses Kafka for ordering, but has v1.1 capabilities enabled.
SampleDevModeKafkaV11Profile = "SampleDevModeKafkaV1_1"
// SampleSingleMSPKafkaProfile references the sample profile which includes only the sample MSP and uses Kafka for ordering.
SampleSingleMSPKafkaProfile = "SampleSingleMSPKafka"
// SampleSingleMSPKafkaV11Profile references the sample profile which includes only the sample MSP with v1.1 capabilities defined and uses Kafka for ordering.
Expand Down
Loading

0 comments on commit 500d3de

Please sign in to comment.