313 lines
10 KiB
Go
313 lines
10 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package smartbft_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric/common/capabilities"
|
|
"github.com/hyperledger/fabric/common/configtx"
|
|
"github.com/hyperledger/fabric/common/flogging"
|
|
"github.com/hyperledger/fabric/core/config/configtest"
|
|
"github.com/hyperledger/fabric/internal/configtxgen/encoder"
|
|
"github.com/hyperledger/fabric/internal/configtxgen/genesisconfig"
|
|
"github.com/hyperledger/fabric/orderer/consensus/smartbft"
|
|
"github.com/hyperledger/fabric/orderer/consensus/smartbft/mocks"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
func TestValidateConfig(t *testing.T) {
|
|
configBlockEnvelope := makeConfigTx("mychannel", 1)
|
|
configBlockEnvelopePayload := protoutil.UnmarshalPayloadOrPanic(configBlockEnvelope.Payload)
|
|
configEnvelope := &cb.ConfigEnvelope{}
|
|
err := proto.Unmarshal(configBlockEnvelopePayload.Data, configEnvelope)
|
|
assert.NoError(t, err)
|
|
|
|
lateConfigEnvelope := proto.Clone(configEnvelope).(*cb.ConfigEnvelope)
|
|
lateConfigEnvelope.Config.Sequence--
|
|
|
|
for _, testCase := range []struct {
|
|
name string
|
|
envelope *cb.Envelope
|
|
mutateEnvelope func(envelope *cb.Envelope)
|
|
applyFiltersReturns error
|
|
proposeConfigUpdateReturns *cb.ConfigEnvelope
|
|
proposeConfigUpdaterr error
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "green path - config block",
|
|
envelope: configBlockEnvelope,
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
mutateEnvelope: func(_ *cb.Envelope) {},
|
|
},
|
|
{
|
|
name: "invalid envelope",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = []byte{1, 2, 3}
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "error unmarshalling Payload: proto: cannot parse invalid wire-format data",
|
|
},
|
|
{
|
|
name: "empty header",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = nil
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "no header was set",
|
|
},
|
|
{
|
|
name: "no channel header",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{Header: &cb.Header{}})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "no channel header was set",
|
|
},
|
|
{
|
|
name: "invalid channel header",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{Header: &cb.Header{
|
|
ChannelHeader: []byte{1, 2, 3},
|
|
}})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "channel header unmarshalling error: error unmarshalling ChannelHeader: " +
|
|
"proto: cannot parse invalid wire-format data",
|
|
},
|
|
{
|
|
name: "invalid payload data",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: &cb.Header{
|
|
ChannelHeader: configBlockEnvelopePayload.Header.ChannelHeader,
|
|
},
|
|
Data: []byte{1, 2, 3},
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "data unmarshalling error: proto: cannot parse invalid wire-format data",
|
|
},
|
|
{
|
|
name: "invalid payload data",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: []byte{1, 2, 3},
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "data unmarshalling error: proto: cannot parse invalid wire-format data",
|
|
},
|
|
{
|
|
name: "invalid payload data",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{}),
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "invalid config envelope",
|
|
},
|
|
{
|
|
name: "invalid payload data",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{}),
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "invalid config envelope",
|
|
},
|
|
{
|
|
name: "invalid payload data",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
|
|
LastUpdate: &cb.Envelope{
|
|
Payload: protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: &cb.Header{
|
|
ChannelHeader: []byte{1, 2, 3},
|
|
},
|
|
}),
|
|
},
|
|
Config: &cb.Config{},
|
|
}),
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "error extracting channel ID from config update",
|
|
},
|
|
{
|
|
name: "invalid inner payload",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
|
|
LastUpdate: &cb.Envelope{
|
|
Payload: []byte{1, 2, 3},
|
|
},
|
|
Config: &cb.Config{},
|
|
}),
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "error unmarshalling Payload: proto: cannot parse invalid wire-format data",
|
|
},
|
|
{
|
|
name: "invalid inner payload header",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
|
|
LastUpdate: &cb.Envelope{
|
|
Payload: protoutil.MarshalOrPanic(&cb.Payload{}),
|
|
},
|
|
Config: &cb.Config{},
|
|
}),
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "inner header is nil",
|
|
},
|
|
{
|
|
name: "invalid inner payload channel header",
|
|
envelope: configBlockEnvelope,
|
|
mutateEnvelope: func(env *cb.Envelope) {
|
|
env.Payload = protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: configBlockEnvelopePayload.Header,
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
|
|
LastUpdate: &cb.Envelope{
|
|
Payload: protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: &cb.Header{},
|
|
}),
|
|
},
|
|
Config: &cb.Config{},
|
|
}),
|
|
})
|
|
},
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
expectedError: "inner channelheader is nil",
|
|
},
|
|
{
|
|
name: "unauthorized path",
|
|
envelope: configBlockEnvelope,
|
|
proposeConfigUpdateReturns: configEnvelope,
|
|
mutateEnvelope: func(_ *cb.Envelope) {},
|
|
applyFiltersReturns: errors.New("unauthorized"),
|
|
expectedError: "unauthorized",
|
|
},
|
|
{
|
|
name: "not up to date config update",
|
|
envelope: configBlockEnvelope,
|
|
proposeConfigUpdateReturns: lateConfigEnvelope,
|
|
mutateEnvelope: func(_ *cb.Envelope) {},
|
|
expectedError: "pending config does not match calculated expected config",
|
|
},
|
|
{
|
|
name: "propose config update fails",
|
|
envelope: configBlockEnvelope,
|
|
proposeConfigUpdaterr: errors.New("error proposing config update"),
|
|
mutateEnvelope: func(_ *cb.Envelope) {},
|
|
expectedError: "error proposing config update",
|
|
},
|
|
} {
|
|
testCase := testCase
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
cup := &mocks.ConfigUpdateProposer{}
|
|
f := &mocks.Filters{}
|
|
b := &mocks.Bundle{}
|
|
ctv := &mocks.ConfigTxValidator{}
|
|
cbv := &smartbft.ConfigBlockValidator{
|
|
Logger: flogging.MustGetLogger("test"),
|
|
ConfigUpdateProposer: cup,
|
|
Filters: f,
|
|
ValidatingChannel: "mychannel",
|
|
}
|
|
env := proto.Clone(testCase.envelope).(*cb.Envelope)
|
|
testCase.mutateEnvelope(env)
|
|
f.On("ApplyFilters", mock.Anything, env).Return(testCase.applyFiltersReturns)
|
|
cup.On("ProposeConfigUpdate", mock.Anything, mock.Anything).
|
|
Return(testCase.proposeConfigUpdateReturns, testCase.proposeConfigUpdaterr)
|
|
b.On("ConfigtxValidator").Return(ctv)
|
|
ctv.On("ProposeConfigUpdate", mock.Anything, mock.Anything).
|
|
Return(testCase.proposeConfigUpdateReturns, testCase.proposeConfigUpdaterr)
|
|
err = cbv.ValidateConfig(env)
|
|
if testCase.expectedError == "" {
|
|
assert.NoError(t, err)
|
|
} else {
|
|
assert.Error(t, err)
|
|
assert.Equal(t, testCase.expectedError, strings.ReplaceAll(err.Error(), "\u00a0", " "))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeConfigTx(chainID string, i int) *cb.Envelope {
|
|
gConf := genesisconfig.Load(genesisconfig.SampleAppChannelSmartBftProfile, configtest.GetDevConfigDir())
|
|
gConf.Orderer.Capabilities = map[string]bool{
|
|
capabilities.OrdererV2_0: true,
|
|
}
|
|
for _, cm := range gConf.Orderer.ConsenterMapping {
|
|
cm.ClientTLSCert = ""
|
|
cm.ServerTLSCert = ""
|
|
cm.Identity = ""
|
|
}
|
|
|
|
channelGroup, err := encoder.NewChannelGroup(gConf)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return makeConfigTxFromConfigUpdateEnvelope(chainID, &cb.ConfigUpdateEnvelope{
|
|
ConfigUpdate: protoutil.MarshalOrPanic(&cb.ConfigUpdate{
|
|
WriteSet: channelGroup,
|
|
}),
|
|
})
|
|
}
|
|
|
|
func makeConfigTxFromConfigUpdateEnvelope(chainID string, configUpdateEnv *cb.ConfigUpdateEnvelope) *cb.Envelope {
|
|
signer := &mocks.SignerSerializer{}
|
|
signer.On("Serialize").Return([]byte{}, nil)
|
|
signer.On("Sign", mock.Anything).Return([]byte{}, nil)
|
|
|
|
configUpdateTx, err := protoutil.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, chainID, signer, configUpdateEnv, 0, 0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
configTx, err := protoutil.CreateSignedEnvelope(cb.HeaderType_CONFIG, chainID, signer, &cb.ConfigEnvelope{
|
|
Config: &cb.Config{Sequence: 1, ChannelGroup: configtx.UnmarshalConfigUpdateOrPanic(configUpdateEnv.ConfigUpdate).WriteSet},
|
|
LastUpdate: configUpdateTx,
|
|
},
|
|
0, 0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return configTx
|
|
}
|