diff --git a/orderer/common/msgprocessor/maintenancefilter.go b/orderer/common/msgprocessor/maintenancefilter.go index 23f5eb617ab..bf05fb39cf4 100644 --- a/orderer/common/msgprocessor/maintenancefilter.go +++ b/orderer/common/msgprocessor/maintenancefilter.go @@ -8,11 +8,14 @@ package msgprocessor import ( "bytes" + "time" + + "github.com/SmartBFT-Go/consensus/pkg/types" "github.com/golang/protobuf/proto" cb "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/orderer" - protoetcdraft "github.com/hyperledger/fabric-protos-go/orderer/etcdraft" + "github.com/hyperledger/fabric-protos-go/orderer/smartbft" "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/common/channelconfig" "github.com/hyperledger/fabric/common/configtx" @@ -45,7 +48,7 @@ func NewMaintenanceFilter(support MaintenanceFilterSupport, bccsp bccsp.BCCSP) * permittedTargetConsensusTypes: make(map[string]bool), bccsp: bccsp, } - mf.permittedTargetConsensusTypes["etcdraft"] = true + // mf.permittedTargetConsensusTypes["etcdraft"] = true mf.permittedTargetConsensusTypes["BFT"] = true return mf } @@ -117,7 +120,7 @@ func (mf *MaintenanceFilter) inspect(configEnvelope *cb.ConfigEnvelope, ordererC } // ConsensusType.Type can only change in maintenance-mode, and only within the set of permitted types. - // Note: only solo to etcdraft transitions are supported. + // Note: only etcdraft to BFT transitions are supported. if ordererConfig.ConsensusType() != nextOrdererConfig.ConsensusType() { if ordererConfig.ConsensusState() == orderer.ConsensusType_STATE_NORMAL { return errors.Errorf("attempted to change consensus type from %s to %s, but current config ConsensusType.State is not in maintenance mode", @@ -133,11 +136,17 @@ func (mf *MaintenanceFilter) inspect(configEnvelope *cb.ConfigEnvelope, ordererC ordererConfig.ConsensusType(), nextOrdererConfig.ConsensusType()) } - if nextOrdererConfig.ConsensusType() == "etcdraft" { - updatedMetadata := &protoetcdraft.ConfigMetadata{} + if nextOrdererConfig.ConsensusType() == "BFT" { + updatedMetadata := &smartbft.Options{} if err := proto.Unmarshal(nextOrdererConfig.ConsensusMetadata(), updatedMetadata); err != nil { - return errors.Wrap(err, "failed to unmarshal etcdraft metadata configuration") + return errors.Wrap(err, "failed to unmarshal BFT metadata configuration") + } + + _, err := validateBFTMetadataOptions(1, updatedMetadata) + if updatedMetadata.XXX_unrecognized != nil || err != nil { + return errors.New("invalid BFT metadata configuration") } + //fmt.Printf("##########the bft metadata is: %+v########\n", updatedMetadata) } logger.Infof("[channel: %s] consensus-type migration: about to change from %s to %s", @@ -170,10 +179,21 @@ func (mf *MaintenanceFilter) ensureConsensusTypeChangeOnly(configEnvelope *cb.Co } if len(configUpdate.WriteSet.Values) > 0 { + //jsonObj, err := json.MarshalIndent(configUpdate, "", " ") + //err = os.WriteFile("configUpdate.json", jsonObj, 0644) + //if err != nil { + // fmt.Println("Error writing JSON to file:", err) + //} return errors.Errorf("config update contains changes to values in group %s", channelconfig.ChannelGroupKey) } if len(configUpdate.WriteSet.Groups) > 1 { + //jsonObj, err := json.MarshalIndent(configUpdate.WriteSet, "", " ") + //err = os.WriteFile("configChangeMaintenanceTest.json", jsonObj, 0644) + //if err != nil { + // fmt.Println("Error writing JSON to file:", err) + //} + //fmt.Printf("------configUpdate.WriteSet.Groups is: %+v, len is: %v\n", configUpdate.WriteSet.Groups, len(configUpdate.WriteSet.Groups)) return errors.New("config update contains changes to more than one group") } @@ -197,3 +217,59 @@ func (mf *MaintenanceFilter) ensureConsensusTypeChangeOnly(configEnvelope *cb.Co return nil } + +func validateBFTMetadataOptions(selfID uint64, options *smartbft.Options) (types.Configuration, error) { + var err error + + config := types.DefaultConfig + config.SelfID = selfID + + if options == nil { + return config, errors.New("config metadata options field is nil") + } + + config.RequestBatchMaxCount = options.RequestBatchMaxCount + config.RequestBatchMaxBytes = options.RequestBatchMaxBytes + if config.RequestBatchMaxInterval, err = time.ParseDuration(options.RequestBatchMaxInterval); err != nil { + return config, errors.Wrap(err, "bad config metadata option RequestBatchMaxInterval") + } + config.IncomingMessageBufferSize = options.IncomingMessageBufferSize + config.RequestPoolSize = options.RequestPoolSize + if config.RequestForwardTimeout, err = time.ParseDuration(options.RequestForwardTimeout); err != nil { + return config, errors.Wrap(err, "bad config metadata option RequestForwardTimeout") + } + if config.RequestComplainTimeout, err = time.ParseDuration(options.RequestComplainTimeout); err != nil { + return config, errors.Wrap(err, "bad config metadata option RequestComplainTimeout") + } + if config.RequestAutoRemoveTimeout, err = time.ParseDuration(options.RequestAutoRemoveTimeout); err != nil { + return config, errors.Wrap(err, "bad config metadata option RequestAutoRemoveTimeout") + } + if config.ViewChangeResendInterval, err = time.ParseDuration(options.ViewChangeResendInterval); err != nil { + return config, errors.Wrap(err, "bad config metadata option ViewChangeResendInterval") + } + if config.ViewChangeTimeout, err = time.ParseDuration(options.ViewChangeTimeout); err != nil { + return config, errors.Wrap(err, "bad config metadata option ViewChangeTimeout") + } + if config.LeaderHeartbeatTimeout, err = time.ParseDuration(options.LeaderHeartbeatTimeout); err != nil { + return config, errors.Wrap(err, "bad config metadata option LeaderHeartbeatTimeout") + } + config.LeaderHeartbeatCount = options.LeaderHeartbeatCount + if config.CollectTimeout, err = time.ParseDuration(options.CollectTimeout); err != nil { + return config, errors.Wrap(err, "bad config metadata option CollectTimeout") + } + config.SyncOnStart = options.SyncOnStart + config.SpeedUpViewChange = options.SpeedUpViewChange + + config.LeaderRotation = false + config.DecisionsPerLeader = 0 + + if err = config.Validate(); err != nil { + return config, errors.Wrap(err, "config validation failed") + } + + if options.RequestMaxBytes == 0 { + config.RequestMaxBytes = config.RequestBatchMaxBytes + } + + return config, nil +} diff --git a/orderer/common/msgprocessor/maintenancefilter_test.go b/orderer/common/msgprocessor/maintenancefilter_test.go index 7bd4a6abff8..b0098cc1c6d 100644 --- a/orderer/common/msgprocessor/maintenancefilter_test.go +++ b/orderer/common/msgprocessor/maintenancefilter_test.go @@ -7,11 +7,18 @@ SPDX-License-Identifier: Apache-2.0 package msgprocessor import ( + "fmt" + "os" + "path" "testing" + "github.com/SmartBFT-Go/consensus/pkg/types" + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric-protos-go/orderer/smartbft" + "github.com/hyperledger/fabric/common/crypto/tlsgen" + "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/orderer" - "github.com/hyperledger/fabric-protos-go/orderer/etcdraft" "github.com/hyperledger/fabric/bccsp/sw" "github.com/hyperledger/fabric/common/capabilities" "github.com/hyperledger/fabric/common/channelconfig" @@ -29,7 +36,7 @@ func newMockOrdererConfig(migration bool, state orderer.ConsensusType_State) *mo mockCapabilities := &mocks.OrdererCapabilities{} mockCapabilities.ConsensusTypeMigrationReturns(migration) mockOrderer.CapabilitiesReturns(mockCapabilities) - mockOrderer.ConsensusTypeReturns("solo") + mockOrderer.ConsensusTypeReturns("etcdraft") mockOrderer.ConsensusStateReturns(state) return mockOrderer } @@ -54,7 +61,7 @@ func TestMaintenanceDisabled(t *testing.T) { require.NoError(t, err) mf := NewMaintenanceFilter(msInactive, cryptoProvider) require.NotNil(t, mf) - current := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} + current := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} t.Run("Good", func(t *testing.T) { configTx := makeConfigEnvelopeWithExtraStuff(t, current, current, 3) @@ -63,7 +70,7 @@ func TestMaintenanceDisabled(t *testing.T) { }) t.Run("Block entry to maintenance", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} + next := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.EqualError(t, err, @@ -71,11 +78,11 @@ func TestMaintenanceDisabled(t *testing.T) { }) t.Run("Block type change", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "BFT", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.EqualError(t, err, - "config transaction inspection failed: next config attempted to change ConsensusType.Type from solo to etcdraft, but capability is disabled") + "config transaction inspection failed: next config attempted to change ConsensusType.Type from etcdraft to BFT, but capability is disabled") }) } @@ -166,29 +173,47 @@ func TestMaintenanceInspectEntry(t *testing.T) { mf := NewMaintenanceFilter(msActive, cryptoProvider) require.NotNil(t, mf) bogusMetadata := []byte{1, 2, 3, 4} - current := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} + validMetadata := []byte{} + bftMetadata := protoutil.MarshalOrPanic(createValidBFTMetadata()) + current := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} t.Run("Good", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} + next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.NoError(t, err) }) t.Run("Bad: concurrent change to consensus type & state", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + next := consensusTypeInfo{ordererType: "BFT", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + configTx := makeConfigEnvelope(t, current, next) + err := mf.Apply(configTx) + require.EqualError(t, err, + "config transaction inspection failed: attempted to change ConsensusType.Type from etcdraft to BFT, but ConsensusType.State is changing from STATE_NORMAL to STATE_MAINTENANCE") + }) + + t.Run("Bad: concurrent change to metadata & state", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "etcdraft", metadata: bftMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.EqualError(t, err, - "config transaction inspection failed: attempted to change ConsensusType.Type from solo to etcdraft, but ConsensusType.State is changing from STATE_NORMAL to STATE_MAINTENANCE") + "config transaction inspection failed: attempted to change ConsensusType.Metadata, but ConsensusType.State is changing from STATE_NORMAL to STATE_MAINTENANCE") + }) + + t.Run("Bad: concurrent change to state & orderer value", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 4) + err := mf.Apply(configTx) + require.EqualError(t, err, + "config transaction inspection failed: config update contains changes to groups within the Orderer group") }) t.Run("Bad: change consensus type not in maintenance", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "BFT", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.EqualError(t, err, - "config transaction inspection failed: attempted to change consensus type from solo to etcdraft, but current config ConsensusType.State is not in maintenance mode") + "config transaction inspection failed: attempted to change consensus type from etcdraft to BFT, but current config ConsensusType.State is not in maintenance mode") }) } @@ -201,53 +226,62 @@ func TestMaintenanceInspectChange(t *testing.T) { mf := NewMaintenanceFilter(msActive, cryptoProvider) require.NotNil(t, mf) bogusMetadata := []byte{1, 2, 3, 4} - validMetadata := protoutil.MarshalOrPanic(&etcdraft.ConfigMetadata{}) - current := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} + validMetadata := protoutil.MarshalOrPanic(createValidBFTMetadata()) + current := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} - t.Run("Good type change", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} - configTx := makeConfigEnvelope(t, current, next) + t.Run("Good type change with valid BFT metadata and consenter mapping", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 4) err := mf.Apply(configTx) require.NoError(t, err) }) t.Run("Good exit, no change", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.NoError(t, err) }) - t.Run("Bad: unsupported consensus type", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "unsupported", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + t.Run("Bad: good type change with invalid BFT metadata", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "BFT", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) + require.Error(t, err) require.EqualError(t, err, - "config transaction inspection failed: attempted to change consensus type from solo to unsupported, transition not supported") + "config transaction inspection failed: invalid BFT metadata configuration") }) - t.Run("Bad: concurrent change to consensus type & state", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} + t.Run("Bad: BFT metadata cannot be unmarshalled", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "BFT", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + configTx := makeConfigEnvelope(t, current, next) + err := mf.Apply(configTx) + require.Error(t, err) + require.Contains(t, err.Error(), + "config transaction inspection failed: failed to unmarshal BFT metadata configuration") + }) + + t.Run("Bad: unsupported consensus type", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "unsupported", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.EqualError(t, err, - "config transaction inspection failed: attempted to change ConsensusType.Type from solo to etcdraft, but ConsensusType.State is changing from STATE_MAINTENANCE to STATE_NORMAL") + "config transaction inspection failed: attempted to change consensus type from etcdraft to unsupported, transition not supported") }) - t.Run("Bad: etcdraft metadata", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: bogusMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + t.Run("Bad: concurrent change to consensus type & state", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) - require.Error(t, err) - require.Contains(t, err.Error(), - "config transaction inspection failed: failed to unmarshal etcdraft metadata configuration") + require.EqualError(t, err, + "config transaction inspection failed: attempted to change ConsensusType.Type from etcdraft to BFT, but ConsensusType.State is changing from STATE_MAINTENANCE to STATE_NORMAL") }) } func TestMaintenanceInspectExit(t *testing.T) { - validMetadata := protoutil.MarshalOrPanic(&etcdraft.ConfigMetadata{}) + validMetadata := protoutil.MarshalOrPanic(createValidBFTMetadata()) mockOrderer := newMockOrdererConfig(true, orderer.ConsensusType_STATE_MAINTENANCE) - mockOrderer.ConsensusTypeReturns("etcdraft") + mockOrderer.ConsensusTypeReturns("BFT") mockOrderer.ConsensusMetadataReturns(validMetadata) msActive := &mockSystemChannelFilterSupport{ @@ -257,43 +291,58 @@ func TestMaintenanceInspectExit(t *testing.T) { require.NoError(t, err) mf := NewMaintenanceFilter(msActive, cryptoProvider) require.NotNil(t, mf) - current := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + current := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} t.Run("Good exit", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.NoError(t, err) }) t.Run("Bad: concurrent change to consensus type & state", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "solo", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelope(t, current, next) err := mf.Apply(configTx) require.EqualError(t, err, - "config transaction inspection failed: attempted to change ConsensusType.Type from etcdraft to solo, but ConsensusType.State is changing from STATE_MAINTENANCE to STATE_NORMAL") + "config transaction inspection failed: attempted to change ConsensusType.Type from BFT to etcdraft, but ConsensusType.State is changing from STATE_MAINTENANCE to STATE_NORMAL") + }) + + t.Run("Bad: change consensus type from BFT to Raft", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "etcdraft", metadata: []byte{}, state: orderer.ConsensusType_STATE_MAINTENANCE} + configTx := makeConfigEnvelope(t, current, next) + err := mf.Apply(configTx) + require.EqualError(t, err, + "config transaction inspection failed: attempted to change consensus type from BFT to etcdraft, transition not supported") }) t.Run("Bad: exit with extra group", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 1) err := mf.Apply(configTx) require.EqualError(t, err, "config transaction inspection failed: config update contains changes to more than one group") }) t.Run("Bad: exit with extra value", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 2) err := mf.Apply(configTx) require.EqualError(t, err, "config transaction inspection failed: config update contains changes to values in group Channel") }) t.Run("Bad: exit with extra orderer value", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 3) err := mf.Apply(configTx) require.EqualError(t, err, "config transaction inspection failed: config update contain more then just the ConsensusType value in the Orderer group") }) + + t.Run("Bad: exit with extra orderer value required for BFT", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_NORMAL} + configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 4) + err := mf.Apply(configTx) + require.EqualError(t, err, "config transaction inspection failed: config update contains changes to groups within the Orderer group") + }) } func TestMaintenanceExtra(t *testing.T) { @@ -304,29 +353,36 @@ func TestMaintenanceExtra(t *testing.T) { require.NoError(t, err) mf := NewMaintenanceFilter(msActive, cryptoProvider) require.NotNil(t, mf) - current := consensusTypeInfo{ordererType: "solo", metadata: nil, state: orderer.ConsensusType_STATE_MAINTENANCE} - validMetadata := protoutil.MarshalOrPanic(&etcdraft.ConfigMetadata{}) + current := consensusTypeInfo{ordererType: "etcdraft", metadata: nil, state: orderer.ConsensusType_STATE_MAINTENANCE} + validMetadata := protoutil.MarshalOrPanic(createValidBFTMetadata()) t.Run("Good: with extra group", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 1) err := mf.Apply(configTx) require.NoError(t, err) }) t.Run("Good: with extra value", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 2) err := mf.Apply(configTx) require.NoError(t, err) }) t.Run("Good: with extra orderer value", func(t *testing.T) { - next := consensusTypeInfo{ordererType: "etcdraft", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 3) err := mf.Apply(configTx) require.NoError(t, err) }) + + t.Run("Good: with extra orderer value required for BFT", func(t *testing.T) { + next := consensusTypeInfo{ordererType: "BFT", metadata: validMetadata, state: orderer.ConsensusType_STATE_MAINTENANCE} + configTx := makeConfigEnvelopeWithExtraStuff(t, current, next, 4) + err := mf.Apply(configTx) + require.NoError(t, err) + }) } func TestMaintenanceMissingConsensusType(t *testing.T) { @@ -337,7 +393,7 @@ func TestMaintenanceMissingConsensusType(t *testing.T) { require.NoError(t, err) mf := NewMaintenanceFilter(msActive, cryptoProvider) require.NotNil(t, mf) - current := consensusTypeInfo{ordererType: "solo", metadata: nil, state: orderer.ConsensusType_STATE_MAINTENANCE} + current := consensusTypeInfo{ordererType: "etcdraft", metadata: nil, state: orderer.ConsensusType_STATE_MAINTENANCE} for i := 1; i < 4; i++ { configTx := makeConfigEnvelopeWithExtraStuff(t, current, current, i) err := mf.Apply(configTx) @@ -353,7 +409,7 @@ type consensusTypeInfo struct { func makeConfigEnvelope(t *testing.T, current, next consensusTypeInfo) *common.Envelope { original := makeBaseConfig(t) - updated := makeBaseConfig(t) + updated := proto.Clone(original).(*common.Config) original.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Values[channelconfig.ConsensusTypeKey] = &common.ConfigValue{ Value: protoutil.MarshalOrPanic( @@ -382,7 +438,7 @@ func makeConfigEnvelope(t *testing.T, current, next consensusTypeInfo) *common.E func makeConfigEnvelopeWithExtraStuff(t *testing.T, current, next consensusTypeInfo, extra int) *common.Envelope { original := makeBaseConfig(t) - updated := makeBaseConfig(t) + updated := proto.Clone(original).(*common.Config) original.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Values[channelconfig.ConsensusTypeKey] = &common.ConfigValue{ Value: protoutil.MarshalOrPanic( @@ -406,7 +462,7 @@ func makeConfigEnvelopeWithExtraStuff(t *testing.T, current, next consensusTypeI switch extra { case 1: - updated.ChannelGroup.Groups[channelconfig.ConsortiumsGroupKey] = &common.ConfigGroup{} + updated.ChannelGroup.Groups[channelconfig.ApplicationGroupKey].Policies = nil case 2: updated.ChannelGroup.Values[channelconfig.ConsortiumKey] = &common.ConfigValue{ Value: protoutil.MarshalOrPanic(&common.Consortium{}), @@ -423,10 +479,27 @@ func makeConfigEnvelopeWithExtraStuff(t *testing.T, current, next consensusTypeI }), ModPolicy: channelconfig.AdminsPolicyKey, } + case 4: + updated.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Values[channelconfig.OrderersKey] = &common.ConfigValue{ + Value: protoutil.MarshalOrPanic(&common.Orderers{ConsenterMapping: []*common.Consenter{{Id: 1, Host: "", Port: 0}}}), + ModPolicy: channelconfig.AdminsPolicyKey, + } default: return nil } + //jsonObj, err := json.MarshalIndent(original, "", " ") + //err = os.WriteFile("original.json", jsonObj, 0644) + //if err != nil { + // fmt.Println("Error writing JSON to file:", err) + //} + // + //jsonObj, err = json.MarshalIndent(updated, "", " ") + //err = os.WriteFile("updated.json", jsonObj, 0644) + //if err != nil { + // fmt.Println("Error writing JSON to file:", err) + //} + configTx := makeConfigTx(original, updated, t) return configTx @@ -438,6 +511,13 @@ func makeConfigTx(original, updated *common.Config, t *testing.T) *common.Envelo configUpdateEnv := &common.ConfigUpdateEnvelope{ ConfigUpdate: protoutil.MarshalOrPanic(configUpdate), } + + //jsonObj, err := json.MarshalIndent(configUpdate, "", " ") + //err = os.WriteFile("configUpdateDiff.json", jsonObj, 0644) + //if err != nil { + // fmt.Println("Error writing JSON to file:", err) + //} + configUpdateTx, err := protoutil.CreateSignedEnvelope(common.HeaderType_CONFIG_UPDATE, testChannelID, &mocks.SignerSerializer{}, configUpdateEnv, 0, 0) require.NoError(t, err) configTx, err := protoutil.CreateSignedEnvelope( @@ -455,11 +535,18 @@ func makeConfigTx(original, updated *common.Config, t *testing.T) *common.Envelo } func makeBaseConfig(t *testing.T) *common.Config { - gConf := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir()) + certDir := t.TempDir() + tlsCA, err := tlsgen.NewCA() + require.NoError(t, err) + + gConf := genesisconfig.Load(genesisconfig.SampleAppChannelEtcdRaftProfile, configtest.GetDevConfigDir()) + generateCertificates(t, gConf, tlsCA, certDir) + gConf.Orderer.Capabilities = map[string]bool{ - capabilities.OrdererV1_4_2: true, + capabilities.ChannelV3_0: true, } - gConf.Orderer.OrdererType = "solo" + + gConf.Orderer.OrdererType = "etcdraft" channelGroup, err := encoder.NewChannelGroup(gConf) require.NoError(t, err) original := &common.Config{ @@ -467,3 +554,43 @@ func makeBaseConfig(t *testing.T) *common.Config { } return original } + +func generateCertificates(t *testing.T, confAppRaft *genesisconfig.Profile, tlsCA tlsgen.CA, certDir string) { + for i, c := range confAppRaft.Orderer.EtcdRaft.Consenters { + srvC, err := tlsCA.NewServerCertKeyPair(c.Host) + require.NoError(t, err) + srvP := path.Join(certDir, fmt.Sprintf("server%d.crt", i)) + err = os.WriteFile(srvP, srvC.Cert, 0o644) + require.NoError(t, err) + + clnC, err := tlsCA.NewClientCertKeyPair() + require.NoError(t, err) + clnP := path.Join(certDir, fmt.Sprintf("client%d.crt", i)) + err = os.WriteFile(clnP, clnC.Cert, 0o644) + require.NoError(t, err) + + c.ServerTlsCert = []byte(srvP) + c.ClientTlsCert = []byte(clnP) + } +} + +func createValidBFTMetadata() *smartbft.Options { + bftMetadata := &smartbft.Options{ + RequestBatchMaxCount: types.DefaultConfig.RequestBatchMaxCount, + RequestBatchMaxBytes: types.DefaultConfig.RequestBatchMaxBytes, + RequestBatchMaxInterval: types.DefaultConfig.RequestBatchMaxInterval.String(), + IncomingMessageBufferSize: types.DefaultConfig.IncomingMessageBufferSize, + RequestPoolSize: types.DefaultConfig.RequestPoolSize, + RequestForwardTimeout: types.DefaultConfig.RequestForwardTimeout.String(), + RequestComplainTimeout: types.DefaultConfig.RequestComplainTimeout.String(), + RequestAutoRemoveTimeout: types.DefaultConfig.RequestAutoRemoveTimeout.String(), + ViewChangeResendInterval: types.DefaultConfig.ViewChangeResendInterval.String(), + ViewChangeTimeout: types.DefaultConfig.ViewChangeTimeout.String(), + LeaderHeartbeatTimeout: types.DefaultConfig.LeaderHeartbeatTimeout.String(), + LeaderHeartbeatCount: types.DefaultConfig.LeaderHeartbeatCount, + CollectTimeout: types.DefaultConfig.CollectTimeout.String(), + SyncOnStart: types.DefaultConfig.SyncOnStart, + SpeedUpViewChange: types.DefaultConfig.SpeedUpViewChange, + } + return bftMetadata +} diff --git a/orderer/consensus/etcdraft/chain.go b/orderer/consensus/etcdraft/chain.go index 7dd4da6ce7a..bad0e134496 100644 --- a/orderer/consensus/etcdraft/chain.go +++ b/orderer/consensus/etcdraft/chain.go @@ -1119,7 +1119,13 @@ func (c *Chain) detectConfChange(block *common.Block) *MembershipChanges { // if the consensusType is bft, then configMetadata which represents the raft metadata should be nil configMetadata, consensusType := c.newConfigMetadata(block) - c.logger.Infof("Detected configuration change: consensusType is: %s, configMetadata is: %v", consensusType.Type, configMetadata) + c.logger.Infof("Detected configuration change: consensusType is: %s, configMetadata is: %v", consensusType, configMetadata) + + if consensusType == nil { + fmt.Printf("Hi May - you got it! the consensus type is nil.\n") + c.logger.Infof("ConsensusType is %v", consensusType) + return nil + } if consensusType.Type != "etcdraft" { if consensusType.Type == "BFT" { @@ -1442,9 +1448,13 @@ func (c *Chain) getInFlightConfChange() *raftpb.ConfChange { func (c *Chain) newConfigMetadata(block *common.Block) (*etcdraft.ConfigMetadata, *orderer.ConsensusType) { c.logger.Infof("Extract config metadata from the configuration block") metadata, consensusType, err := ConsensusMetadataFromConfigBlock(block) + fmt.Printf("-------newConfigMetadata: the metadata is: %v-------\n", metadata) + fmt.Printf("-------newConfigMetadata: the consensusType is: %v-------\n", consensusType) + fmt.Printf("-------newConfigMetadata: the err is: %v-------\n", err) if err != nil { c.logger.Panicf("error reading consensus metadata: %s", err) } + // c.logger.Infof("Extracted config metadata from the configuration block: metadata is: %v, consensusType is: %s", metadata, consensusType.Type) return metadata, consensusType } @@ -1467,7 +1477,7 @@ func (c *Chain) ValidateConsensusMetadata(oldOrdererConfig, newOrdererConfig cha return nil } else { c.logger.Panicf("illegal consensus type detected during consensus metadata validation: %s", newOrdererConfig.ConsensusType()) - panic("illegal consensus type detected during consensus metadata validation") + return errors.Errorf("illegal consensus type detected during consensus metadata validation: %s", newOrdererConfig.ConsensusType()) } } diff --git a/orderer/consensus/etcdraft/chain_test.go b/orderer/consensus/etcdraft/chain_test.go index bc1ea8a4e16..8f7b93fa05f 100644 --- a/orderer/consensus/etcdraft/chain_test.go +++ b/orderer/consensus/etcdraft/chain_test.go @@ -552,11 +552,17 @@ var _ = Describe("Chain", func() { }, "ConsensusType": { Version: 4, + Value: marshalOrPanic(&orderer.ConsensusType{ + Type: "etcdraft", + }), }, } oldValues := map[string]*common.ConfigValue{ "ConsensusType": { Version: 4, + Value: marshalOrPanic(&orderer.ConsensusType{ + Type: "etcdraft", + }), }, } configEnv = newConfigEnv(channelID, @@ -569,12 +575,15 @@ var _ = Describe("Chain", func() { Context("without revalidation (i.e. correct config sequence)", func() { Context("without pending normal envelope", func() { It("should create a config block and no normal block", func() { + fmt.Printf("$$$$$$$$$$$$$$$$$$$$$-line1-$$$$$$$$$$$$\n") err := chain.Configure(configEnv, configSeq) Expect(err).NotTo(HaveOccurred()) Expect(fakeFields.fakeConfigProposalsReceived.AddCallCount()).To(Equal(1)) Expect(fakeFields.fakeConfigProposalsReceived.AddArgsForCall(0)).To(Equal(float64(1))) + fmt.Printf("$$$$$$$$$$$$$$$$$$$$$-line2-$$$$$$$$$$$$\n") Eventually(support.WriteConfigBlockCallCount, LongEventualTimeout).Should(Equal(1)) Consistently(support.WriteBlockCallCount).Should(Equal(0)) + fmt.Printf("$$$$$$$$$$$$$$$$$$$$$-line3-$$$$$$$$$$$$\n") Expect(fakeFields.fakeCommittedBlockNumber.SetCallCount()).Should(Equal(2)) // incl. initial call Expect(fakeFields.fakeCommittedBlockNumber.SetArgsForCall(1)).Should(Equal(float64(1))) }) diff --git a/orderer/consensus/etcdraft/util.go b/orderer/consensus/etcdraft/util.go index 327b9d8f8ac..c45e6dd50b9 100644 --- a/orderer/consensus/etcdraft/util.go +++ b/orderer/consensus/etcdraft/util.go @@ -9,10 +9,9 @@ package etcdraft import ( "crypto/x509" "encoding/pem" + "fmt" "time" - "github.com/hyperledger/fabric-protos-go/orderer/smartbft" - "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/orderer" @@ -86,6 +85,7 @@ func MetadataHasDuplication(md *etcdraft.ConfigMetadata) error { } // MetadataFromConfigValue reads and translates configuration updates from config value into raft metadata +// In case consensus type is changed to BFT the raft metadata will be nil func MetadataFromConfigValue(configValue *common.ConfigValue) (*etcdraft.ConfigMetadata, *orderer.ConsensusType, error) { consensusTypeValue := &orderer.ConsensusType{} if err := proto.Unmarshal(configValue.Value, consensusTypeValue); err != nil { @@ -94,12 +94,8 @@ func MetadataFromConfigValue(configValue *common.ConfigValue) (*etcdraft.ConfigM if consensusTypeValue.Type != "etcdraft" { if consensusTypeValue.Type == "BFT" { - updatedBftMetadata := &smartbft.Options{} - if err := proto.Unmarshal(consensusTypeValue.Metadata, updatedBftMetadata); err != nil { - return nil, nil, errors.Wrap(err, "failed to unmarshal updated (new) bft metadata configuration") - } + return nil, consensusTypeValue, nil } - return nil, consensusTypeValue, nil } updatedMetadata := &etcdraft.ConfigMetadata{} @@ -120,7 +116,6 @@ func MetadataFromConfigUpdate(update *common.ConfigUpdate) (*etcdraft.ConfigMeta } } } - if update.WriteSet != nil && update.WriteSet.Groups != nil { if ordererConfigGroup, ok := update.WriteSet.Groups["Orderer"]; ok { if val, ok := ordererConfigGroup.Values["ConsensusType"]; ok { @@ -204,6 +199,11 @@ func ConsensusMetadataFromConfigBlock(block *common.Block) (*etcdraft.ConfigMeta return nil, nil, errors.Wrap(err, "could not read config update") } + fmt.Printf("----------IM HERE 1-------------") + metadata, consensusType, err := MetadataFromConfigUpdate(configUpdate) + fmt.Printf("-------ConsensusMetadataFromConfigBlock: the metadata is: %v-------\n", metadata) + fmt.Printf("-------ConsensusMetadataFromConfigBlock: the consensusType is: %v-------\n", consensusType) + fmt.Printf("-------ConsensusMetadataFromConfigBlock: the err is: %v-------\n", err) return MetadataFromConfigUpdate(configUpdate) } diff --git a/orderer/consensus/etcdraft/util_test.go b/orderer/consensus/etcdraft/util_test.go index fbf42a642a1..868153c228e 100644 --- a/orderer/consensus/etcdraft/util_test.go +++ b/orderer/consensus/etcdraft/util_test.go @@ -410,3 +410,15 @@ func TestVerifyConfigMetadata(t *testing.T) { require.Nil(t, VerifyConfigMetadata(metadataWithExpiredConsenter, goodVerifyingOpts)) }) } + +func TestMetadataFromConfigValue(t *testing.T) { + configValue := &common.ConfigValue{ + Value: protoutil.MarshalOrPanic(&common.BlockData{Data: [][]byte{[]byte("BFT")}}), + } + + metadata, consensusType, err := MetadataFromConfigValue(configValue) + require.Nil(t, metadata) + require.Nil(t, err) + require.NotNil(t, consensusType) + require.Equal(t, consensusType.Type, "BFT") +}