Skip to content

Commit

Permalink
[FAB-6072] Panic on incompatibilities
Browse files Browse the repository at this point in the history
Although gracefully leaving a channel might be a better option in the
future, because this behavior is not compatible with v1.0.x gossip, and
because of the complexity of this operation, a good first pass for v1.1
is simply to ensure that all processes panic if incompatible
configuration is pushed to the channel.

This CR causes the orderer to check for capabilities during the config
validatiion process it already undertakes.  It also causes the orderer
to panic if a config block is committed which contains capabilities
unknown to the orderer.

Finally, on join channel and on config update, the peer checks the
capabilities and panics on incompatibility.

Change-Id: Ieeeaf7a1924c42e001a3a973873a4343f134a0ae
Signed-off-by: Binh Q. Nguyen <binhn@us.ibm.com>
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Sep 29, 2017
1 parent 456283e commit 366e978
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 69 deletions.
11 changes: 11 additions & 0 deletions common/mocks/config/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,14 @@ func (scm *Channel) OrdererAddresses() []string {
func (scm *Channel) Capabilities() channelconfig.ChannelCapabilities {
return scm.CapabilitiesVal
}

// ChannelCapabilities mocks the channelconfig.ChannelCapabilities interface
type ChannelCapabilities struct {
// SupportedErr is returned by Supported()
SupportedErr error
}

// Supported returns SupportedErr
func (oc *ChannelCapabilities) Supported() error {
return oc.SupportedErr
}
11 changes: 11 additions & 0 deletions common/mocks/config/orderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,14 @@ func (scm *Orderer) Organizations() map[string]channelconfig.Org {
func (scm *Orderer) Capabilities() channelconfig.OrdererCapabilities {
return scm.CapabilitiesVal
}

// OrdererCapabilities mocks the channelconfig.OrdererCapabilities interface
type OrdererCapabilities struct {
// SupportedErr is returned by Supported()
SupportedErr error
}

// Supported returns SupportedErr
func (oc *OrdererCapabilities) Supported() error {
return oc.SupportedErr
}
4 changes: 2 additions & 2 deletions common/mocks/config/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ func (r *Resources) ChannelConfig() channelconfig.Channel {

// Returns the OrdererConfigVal
func (r *Resources) OrdererConfig() (channelconfig.Orderer, bool) {
return r.OrdererConfigVal, r.OrdererConfigVal == nil
return r.OrdererConfigVal, r.OrdererConfigVal != nil
}

// Returns the ApplicationConfigVal
func (r *Resources) ApplicationConfig() (channelconfig.Application, bool) {
return r.ApplicationConfigVal, r.ApplicationConfigVal == nil
return r.ApplicationConfigVal, r.ApplicationConfigVal != nil
}

func (r *Resources) ConsortiumsConfig() (channelconfig.Consortiums, bool) {
Expand Down
22 changes: 21 additions & 1 deletion core/peer/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,29 @@ func (cs *chainSupport) Apply(configtx *common.ConfigEnvelope) error {
if err != nil {
return err
}

capabilitiesSupportedOrPanic(bundle)

cs.bundleSource.Update(bundle)
}
return nil
}

func capabilitiesSupportedOrPanic(res channelconfig.Resources) {
ac, ok := res.ApplicationConfig()
if !ok {
peerLogger.Panicf("[channel %s] does not have application config so is incompatible", res.ConfigtxManager().ChainID())
}

if err := ac.Capabilities().Supported(); err != nil {
peerLogger.Panicf("[channel %s] incompatible %s", res.ConfigtxManager(), err)
}

if err := res.ChannelConfig().Capabilities().Supported(); err != nil {
peerLogger.Panicf("[channel %s] incompatible %s", res.ConfigtxManager(), err)
}
}

func (cs *chainSupport) Ledger() ledger.PeerLedger {
return cs.ledger
}
Expand Down Expand Up @@ -171,7 +189,7 @@ func Initialize(init func(string)) {
}
}

// Take care to initialize chain after peer joined, for example deploys system CCs
// InitChain takes care to initialize chain after peer joined, for example deploys system CCs
func InitChain(cid string) {
if chainInitializer != nil {
// Initialize chaincode, namely deploy system CC
Expand Down Expand Up @@ -222,6 +240,8 @@ func createChain(cid string, ledger ledger.PeerLedger, cb *common.Block) error {
return err
}

capabilitiesSupportedOrPanic(bundle)

channelconfig.LogSanityChecks(bundle)

gossipEventer := service.GetGossipService().NewConfigEventer()
Expand Down
10 changes: 5 additions & 5 deletions orderer/common/msgprocessor/systemchannel.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
// ChannelConfigTemplator can be used to generate config templates.
type ChannelConfigTemplator interface {
// NewChannelConfig creates a new template configuration manager.
NewChannelConfig(env *cb.Envelope) (configtxapi.Manager, error)
NewChannelConfig(env *cb.Envelope) (channelconfig.Resources, error)
}

// SystemChannel implements the Processor interface for the system channel.
Expand Down Expand Up @@ -95,12 +95,12 @@ func (s *SystemChannel) ProcessConfigUpdateMsg(envConfigUpdate *cb.Envelope) (co

// If the channel ID does not match the system channel, then this must be a channel creation transaction

ctxm, err := s.templator.NewChannelConfig(envConfigUpdate)
bundle, err := s.templator.NewChannelConfig(envConfigUpdate)
if err != nil {
return nil, 0, err
}

newChannelConfigEnv, err := ctxm.ProposeConfigUpdate(envConfigUpdate)
newChannelConfigEnv, err := bundle.ConfigtxManager().ProposeConfigUpdate(envConfigUpdate)
if err != nil {
return nil, 0, err
}
Expand Down Expand Up @@ -205,7 +205,7 @@ func NewDefaultTemplator(support DefaultTemplatorSupport) *DefaultTemplator {
}

// NewChannelConfig creates a new template channel configuration based on the current config in the ordering system channel.
func (dt *DefaultTemplator) NewChannelConfig(envConfigUpdate *cb.Envelope) (configtxapi.Manager, error) {
func (dt *DefaultTemplator) NewChannelConfig(envConfigUpdate *cb.Envelope) (channelconfig.Resources, error) {
configUpdatePayload, err := utils.UnmarshalPayload(envConfigUpdate.Payload)
if err != nil {
return nil, fmt.Errorf("Failing initial channel config creation because of payload unmarshaling error: %s", err)
Expand Down Expand Up @@ -323,5 +323,5 @@ func (dt *DefaultTemplator) NewChannelConfig(envConfigUpdate *cb.Envelope) (conf
return nil, err
}

return bundle.ConfigtxManager(), nil
return bundle, nil
}
8 changes: 5 additions & 3 deletions orderer/common/msgprocessor/systemchannel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (

"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/configtx"
configtxapi "github.com/hyperledger/fabric/common/configtx/api"
"github.com/hyperledger/fabric/common/crypto"
mockchannelconfig "github.com/hyperledger/fabric/common/mocks/config"
mockconfigtx "github.com/hyperledger/fabric/common/mocks/configtx"
genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig"
"github.com/hyperledger/fabric/common/tools/configtxgen/provisional"
Expand All @@ -27,8 +27,10 @@ type mockSystemChannelSupport struct {
NewChannelConfigErr error
}

func (mscs *mockSystemChannelSupport) NewChannelConfig(env *cb.Envelope) (configtxapi.Manager, error) {
return mscs.NewChannelConfigVal, mscs.NewChannelConfigErr
func (mscs *mockSystemChannelSupport) NewChannelConfig(env *cb.Envelope) (channelconfig.Resources, error) {
return &mockchannelconfig.Resources{
ConfigtxManagerVal: mscs.NewChannelConfigVal,
}, mscs.NewChannelConfigErr
}

func TestProcessSystemChannelNormalMsg(t *testing.T) {
Expand Down
91 changes: 47 additions & 44 deletions orderer/common/msgprocessor/systemchannelfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@ SPDX-License-Identifier: Apache-2.0
package msgprocessor

import (
"fmt"

"github.com/hyperledger/fabric/common/channelconfig"
configtxapi "github.com/hyperledger/fabric/common/configtx/api"
cb "github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/utils"

"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
)

// ChainCreator defines the methods necessary to simulate channel creation.
type ChainCreator interface {
// NewChannelConfig returns a template config for a new channel.
NewChannelConfig(envConfigUpdate *cb.Envelope) (configtxapi.Manager, error)
NewChannelConfig(envConfigUpdate *cb.Envelope) (channelconfig.Resources, error)

// CreateBundle parses the config into resources
CreateBundle(channelID string, config *cb.Config) (channelconfig.Resources, error)

// ChannelsCount returns the count of channels which currently exist.
ChannelsCount() int
Expand Down Expand Up @@ -51,16 +52,16 @@ func (scf *SystemChainFilter) Apply(env *cb.Envelope) error {

err := proto.Unmarshal(env.Payload, msgData)
if err != nil {
return fmt.Errorf("bad payload: %s", err)
return errors.Errorf("bad payload: %s", err)
}

if msgData.Header == nil {
return fmt.Errorf("missing payload header")
return errors.Errorf("missing payload header")
}

chdr, err := utils.UnmarshalChannelHeader(msgData.Header.ChannelHeader)
if err != nil {
return fmt.Errorf("bad channel header: %s", err)
return errors.Errorf("bad channel header: %s", err)
}

if chdr.Type != int32(cb.HeaderType_ORDERER_TRANSACTION) {
Expand All @@ -76,83 +77,85 @@ func (scf *SystemChainFilter) Apply(env *cb.Envelope) error {
if maxChannels > 0 {
// We check for strictly greater than to accommodate the system channel
if uint64(scf.cc.ChannelsCount()) > maxChannels {
return fmt.Errorf("channel creation would exceed maximimum number of channels: %d", maxChannels)
return errors.Errorf("channel creation would exceed maximimum number of channels: %d", maxChannels)
}
}

configTx := &cb.Envelope{}
err = proto.Unmarshal(msgData.Data, configTx)
if err != nil {
return fmt.Errorf("payload data error unmarshaling to envelope: %s", err)
return errors.Errorf("payload data error unmarshaling to envelope: %s", err)
}

return scf.authorizeAndInspect(configTx)
}

func (scf *SystemChainFilter) authorize(configEnvelope *cb.ConfigEnvelope) (*cb.ConfigEnvelope, error) {
if configEnvelope.LastUpdate == nil {
return nil, fmt.Errorf("updated config does not include a config update")
}

configManager, err := scf.cc.NewChannelConfig(configEnvelope.LastUpdate)
if err != nil {
return nil, fmt.Errorf("error constructing new channel config from update: %s", err)
}

newChannelConfigEnv, err := configManager.ProposeConfigUpdate(configEnvelope.LastUpdate)
if err != nil {
return nil, fmt.Errorf("error proposing channel update to new channel config: %s", err)
}

err = configManager.Validate(newChannelConfigEnv)
if err != nil {
return nil, fmt.Errorf("error applying channel update to new channel config: %s", err)
}

return newChannelConfigEnv, nil
}

func (scf *SystemChainFilter) authorizeAndInspect(configTx *cb.Envelope) error {
payload := &cb.Payload{}
err := proto.Unmarshal(configTx.Payload, payload)
if err != nil {
return fmt.Errorf("error unmarshaling wrapped configtx envelope payload: %s", err)
return errors.Errorf("error unmarshaling wrapped configtx envelope payload: %s", err)
}

if payload.Header == nil {
return fmt.Errorf("wrapped configtx envelope missing header")
return errors.Errorf("wrapped configtx envelope missing header")
}

chdr, err := utils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
if err != nil {
return fmt.Errorf("error unmarshaling wrapped configtx envelope channel header: %s", err)
return errors.Errorf("error unmarshaling wrapped configtx envelope channel header: %s", err)
}

if chdr.Type != int32(cb.HeaderType_CONFIG) {
return fmt.Errorf("wrapped configtx envelope not a config transaction")
return errors.Errorf("wrapped configtx envelope not a config transaction")
}

configEnvelope := &cb.ConfigEnvelope{}
err = proto.Unmarshal(payload.Data, configEnvelope)
if err != nil {
return fmt.Errorf("error unmarshalling wrapped configtx config envelope from payload: %s", err)
return errors.Errorf("error unmarshalling wrapped configtx config envelope from payload: %s", err)
}

if configEnvelope.LastUpdate == nil {
return errors.Errorf("updated config does not include a config update")
}

res, err := scf.cc.NewChannelConfig(configEnvelope.LastUpdate)
if err != nil {
return errors.Errorf("error constructing new channel config from update: %s", err)
}

// Make sure that the config was signed by the appropriate authorized entities
proposedEnv, err := scf.authorize(configEnvelope)
newChannelConfigEnv, err := res.ConfigtxManager().ProposeConfigUpdate(configEnvelope.LastUpdate)
if err != nil {
return err
return errors.Errorf("error proposing channel update to new channel config: %s", err)
}

// reflect.DeepEqual will not work here, because it considers nil and empty maps as unequal
if !proto.Equal(proposedEnv.Config, configEnvelope.Config) {
return fmt.Errorf("config proposed by the channel creation request did not match the config received with the channel creation request")
if !proto.Equal(newChannelConfigEnv, configEnvelope) {
return errors.Errorf("config proposed by the channel creation request did not match the config received with the channel creation request")
}

// Make sure the config can be parsed into a bundle
_, err = channelconfig.NewBundle(chdr.ChannelId, configEnvelope.Config)
bundle, err := scf.cc.CreateBundle(res.ConfigtxManager().ChainID(), newChannelConfigEnv.Config)
if err != nil {
return fmt.Errorf("failed to create config bundle: %s", err)
return errors.Wrap(err, "config does not validly parse")
}

if err = res.ValidateNew(bundle); err != nil {
return errors.Wrap(err, "new bundle invalid")
}

oc, ok := bundle.OrdererConfig()
if !ok {
return errors.New("config is missing orderer group")
}

if err = oc.Capabilities().Supported(); err != nil {
return errors.Wrap(err, "config update is not compatible")
}

if err = bundle.ChannelConfig().Capabilities().Supported(); err != nil {
return errors.Wrap(err, "config update is not compatible")
}

return nil
Expand Down
27 changes: 21 additions & 6 deletions orderer/common/msgprocessor/systemchannelfilter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/configtx"
configtxapi "github.com/hyperledger/fabric/common/configtx/api"
"github.com/hyperledger/fabric/common/crypto"
mockconfig "github.com/hyperledger/fabric/common/mocks/config"
mockconfigtx "github.com/hyperledger/fabric/common/mocks/configtx"
Expand Down Expand Up @@ -85,15 +84,31 @@ func (mcc *mockChainCreator) ChannelsCount() int {
return len(mcc.newChains)
}

func (mcc *mockChainCreator) NewChannelConfig(envConfigUpdate *cb.Envelope) (configtxapi.Manager, error) {
func (mcc *mockChainCreator) CreateBundle(channelID string, config *cb.Config) (channelconfig.Resources, error) {
return &mockconfig.Resources{
ConfigtxManagerVal: &mockconfigtx.Manager{
ChainIDVal: channelID,
},
OrdererConfigVal: &mockconfig.Orderer{
CapabilitiesVal: &mockconfig.OrdererCapabilities{},
},
ChannelConfigVal: &mockconfig.Channel{
CapabilitiesVal: &mockconfig.ChannelCapabilities{},
},
}, nil
}

func (mcc *mockChainCreator) NewChannelConfig(envConfigUpdate *cb.Envelope) (channelconfig.Resources, error) {
if mcc.NewChannelConfigErr != nil {
return nil, mcc.NewChannelConfigErr
}
confUpdate := configtx.UnmarshalConfigUpdateOrPanic(configtx.UnmarshalConfigUpdateEnvelopeOrPanic(utils.UnmarshalPayloadOrPanic(envConfigUpdate.Payload).Data).ConfigUpdate)
return &mockconfigtx.Manager{
ProposeConfigUpdateVal: &cb.ConfigEnvelope{
Config: &cb.Config{Sequence: 1, ChannelGroup: confUpdate.WriteSet},
LastUpdate: envConfigUpdate,
return &mockconfig.Resources{
ConfigtxManagerVal: &mockconfigtx.Manager{
ProposeConfigUpdateVal: &cb.ConfigEnvelope{
Config: &cb.Config{Sequence: 1, ChannelGroup: confUpdate.WriteSet},
LastUpdate: envConfigUpdate,
},
},
}, nil
}
Expand Down
8 changes: 8 additions & 0 deletions orderer/common/multichannel/chainsupport.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/hyperledger/fabric/orderer/consensus"
cb "github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/utils"

"github.com/pkg/errors"
)

// ChainSupport holds the resources for a particular channel.
Expand Down Expand Up @@ -101,10 +103,16 @@ func (cs *ChainSupport) ProposeConfigUpdate(configtx *cb.Envelope) (*cb.ConfigEn
if err != nil {
return nil, err
}

bundle, err := cs.CreateBundle(cs.ChainID(), env.Config)
if err != nil {
return nil, err
}

if err = checkResources(bundle); err != nil {
return nil, errors.Wrap(err, "config update is not compatible")
}

return env, cs.ValidateNew(bundle)
}

Expand Down
Loading

0 comments on commit 366e978

Please sign in to comment.