github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/consensus/etcdraft/consenter_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package etcdraft_test
     8  
     9  import (
    10  	"encoding/pem"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/golang/protobuf/proto"
    20  	"github.com/hechain20/hechain/bccsp/sw"
    21  	"github.com/hechain20/hechain/common/channelconfig"
    22  	"github.com/hechain20/hechain/common/crypto/tlsgen"
    23  	"github.com/hechain20/hechain/common/flogging"
    24  	"github.com/hechain20/hechain/common/ledger/testutil"
    25  	"github.com/hechain20/hechain/core/config/configtest"
    26  	"github.com/hechain20/hechain/internal/configtxgen/encoder"
    27  	"github.com/hechain20/hechain/internal/configtxgen/genesisconfig"
    28  	"github.com/hechain20/hechain/internal/pkg/comm"
    29  	"github.com/hechain20/hechain/orderer/common/cluster"
    30  	clustermocks "github.com/hechain20/hechain/orderer/common/cluster/mocks"
    31  	"github.com/hechain20/hechain/orderer/common/types"
    32  	"github.com/hechain20/hechain/orderer/consensus"
    33  	"github.com/hechain20/hechain/orderer/consensus/etcdraft"
    34  	"github.com/hechain20/hechain/orderer/consensus/etcdraft/mocks"
    35  	"github.com/hechain20/hechain/orderer/consensus/inactive"
    36  	consensusmocks "github.com/hechain20/hechain/orderer/consensus/mocks"
    37  	"github.com/hechain20/hechain/protoutil"
    38  	"github.com/hyperledger/fabric-protos-go/common"
    39  	"github.com/hyperledger/fabric-protos-go/orderer"
    40  	etcdraftproto "github.com/hyperledger/fabric-protos-go/orderer/etcdraft"
    41  	. "github.com/onsi/ginkgo"
    42  	. "github.com/onsi/ginkgo/extensions/table"
    43  	. "github.com/onsi/gomega"
    44  	gtypes "github.com/onsi/gomega/types"
    45  	"github.com/pkg/errors"
    46  	"github.com/stretchr/testify/mock"
    47  	"go.uber.org/zap"
    48  	"go.uber.org/zap/zapcore"
    49  )
    50  
    51  var certAsPEM []byte
    52  
    53  //go:generate counterfeiter -o mocks/orderer_capabilities.go --fake-name OrdererCapabilities . ordererCapabilities
    54  type ordererCapabilities interface {
    55  	channelconfig.OrdererCapabilities
    56  }
    57  
    58  //go:generate counterfeiter -o mocks/orderer_config.go --fake-name OrdererConfig . ordererConfig
    59  type ordererConfig interface {
    60  	channelconfig.Orderer
    61  }
    62  
    63  var _ = Describe("Consenter", func() {
    64  	var (
    65  		chainManager *mocks.ChainManager
    66  		support      *consensusmocks.FakeConsenterSupport
    67  		dataDir      string
    68  		snapDir      string
    69  		walDir       string
    70  		tlsCA        tlsgen.CA
    71  	)
    72  
    73  	BeforeEach(func() {
    74  		var err error
    75  		tlsCA, err = tlsgen.NewCA()
    76  		Expect(err).NotTo(HaveOccurred())
    77  		kp, err := tlsCA.NewClientCertKeyPair()
    78  		Expect(err).NotTo(HaveOccurred())
    79  		if certAsPEM == nil {
    80  			certAsPEM = kp.Cert
    81  		}
    82  		chainManager = &mocks.ChainManager{}
    83  		support = &consensusmocks.FakeConsenterSupport{}
    84  		dataDir, err = ioutil.TempDir("", "consenter-")
    85  		Expect(err).NotTo(HaveOccurred())
    86  		walDir = path.Join(dataDir, "wal-")
    87  		snapDir = path.Join(dataDir, "snap-")
    88  
    89  		blockBytes, err := ioutil.ReadFile("testdata/mychannel.block")
    90  		Expect(err).NotTo(HaveOccurred())
    91  
    92  		goodConfigBlock := &common.Block{}
    93  		proto.Unmarshal(blockBytes, goodConfigBlock)
    94  
    95  		lastBlock := &common.Block{
    96  			Header: &common.BlockHeader{
    97  				Number: 1,
    98  			},
    99  			Data: goodConfigBlock.Data,
   100  			Metadata: &common.BlockMetadata{
   101  				Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{
   102  					Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 0}),
   103  				})},
   104  			},
   105  		}
   106  
   107  		support.BlockReturns(lastBlock)
   108  	})
   109  
   110  	AfterEach(func() {
   111  		if dataDir != "" {
   112  			os.RemoveAll(dataDir)
   113  		}
   114  	})
   115  
   116  	When("the consenter is extracting the channel", func() {
   117  		It("extracts successfully from step requests", func() {
   118  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   119  			ch := consenter.TargetChannel(&orderer.ConsensusRequest{Channel: "mychannel"})
   120  			Expect(ch).To(BeIdenticalTo("mychannel"))
   121  		})
   122  
   123  		It("extracts successfully from submit requests", func() {
   124  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   125  			ch := consenter.TargetChannel(&orderer.SubmitRequest{Channel: "mychannel"})
   126  			Expect(ch).To(BeIdenticalTo("mychannel"))
   127  		})
   128  
   129  		It("returns an empty string for the rest of the messages", func() {
   130  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   131  			ch := consenter.TargetChannel(&common.Block{})
   132  			Expect(ch).To(BeEmpty())
   133  		})
   134  	})
   135  
   136  	DescribeTable("identifies a bad block",
   137  		func(block *common.Block, errMatcher gtypes.GomegaMatcher) {
   138  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   139  			isMem, err := consenter.IsChannelMember(block)
   140  			Expect(isMem).To(BeFalse())
   141  			Expect(err).To(errMatcher)
   142  		},
   143  		Entry("nil block", nil, MatchError("nil block")),
   144  		Entry("data is nil", &common.Block{}, MatchError("block data is nil")),
   145  		Entry("data is empty", protoutil.NewBlock(10, []byte{1, 2, 3, 4}), MatchError("envelope index out of bounds")),
   146  		Entry("bad data",
   147  			func() *common.Block {
   148  				block := protoutil.NewBlock(10, []byte{1, 2, 3, 4})
   149  				block.Data.Data = [][]byte{{1, 2, 3, 4}, {5, 6, 7, 8}}
   150  				return block
   151  			}(),
   152  			MatchError(HavePrefix("block data does not carry an envelope at index 0: error unmarshalling Envelope: proto:"))),
   153  	)
   154  
   155  	When("the consenter is asked about join-block membership", func() {
   156  		var (
   157  			mspDir          string
   158  			memberKeyPair   *tlsgen.CertKeyPair
   159  			genesisBlockApp *common.Block
   160  			confAppRaft     *genesisconfig.Profile
   161  		)
   162  
   163  		BeforeEach(func() {
   164  			var err error
   165  			mspDir, err = ioutil.TempDir(dataDir, "msp")
   166  			Expect(err).NotTo(HaveOccurred())
   167  
   168  			confAppRaft = genesisconfig.Load(genesisconfig.SampleDevModeEtcdRaftProfile, configtest.GetDevConfigDir())
   169  			confAppRaft.Consortiums = nil
   170  			confAppRaft.Consortium = ""
   171  
   172  			// IsChannelMember verifies config meta along with it's tls certs
   173  			// ofconsenters. So when we add new conseter with tls certs, they must be
   174  			// signed by any msp from orderer config. Consenters in this test will
   175  			// have certificates from fixtures generated by tlsgen pkg. To pass
   176  			// validation, root ca cert should be part of a MSP in orderer config.
   177  			// Adding tls ca root cert to an existing ordering org's MSP definition.
   178  			Expect(confAppRaft.Orderer).NotTo(BeNil())
   179  			Expect(confAppRaft.Orderer.Organizations).ToNot(HaveLen(0))
   180  			Expect(confAppRaft.Orderer.EtcdRaft.Consenters).ToNot(HaveLen(0))
   181  
   182  			// one consenter is enough for testing
   183  			confAppRaft.Orderer.EtcdRaft.Consenters = confAppRaft.Orderer.EtcdRaft.Consenters[:1]
   184  
   185  			// Generate client pair using tlsCA and set it to the consenter
   186  			memberKeyPair, err = tlsCA.NewServerCertKeyPair("127.0.0.1", "::1", "localhost")
   187  			Expect(err).NotTo(HaveOccurred())
   188  			consenterDir, err := ioutil.TempDir(dataDir, "consenter")
   189  			Expect(err).NotTo(HaveOccurred())
   190  			consenterCertPath := filepath.Join(consenterDir, "client.pem")
   191  			err = ioutil.WriteFile(consenterCertPath, memberKeyPair.Cert, 0o644)
   192  			Expect(err).NotTo(HaveOccurred())
   193  
   194  			confAppRaft.Orderer.EtcdRaft.Consenters[0].ClientTlsCert = []byte(consenterCertPath)
   195  			confAppRaft.Orderer.EtcdRaft.Consenters[0].ServerTlsCert = []byte(consenterCertPath)
   196  
   197  			// Don't want to spoil sampleconfig, copying it to some tmp dir.
   198  			err = testutil.CopyDir(confAppRaft.Orderer.Organizations[0].MSPDir, mspDir, true)
   199  			Expect(err).NotTo(HaveOccurred())
   200  			confAppRaft.Orderer.Organizations[0].MSPDir = mspDir
   201  			confAppRaft.Orderer.Organizations[0].ID = fmt.Sprintf("SampleMSP-%d", time.Now().UnixNano())
   202  
   203  			// Write the TLS root cert to the msp folder
   204  			err = ioutil.WriteFile(filepath.Join(mspDir, "tlscacerts", "cert.pem"), tlsCA.CertBytes(), 0o644)
   205  			Expect(err).NotTo(HaveOccurred())
   206  
   207  			bootstrapper, err := encoder.NewBootstrapper(confAppRaft)
   208  			Expect(err).NotTo(HaveOccurred())
   209  			genesisBlockApp = bootstrapper.GenesisBlockForChannel("my-raft-channel")
   210  			Expect(genesisBlockApp).NotTo(BeNil())
   211  		})
   212  
   213  		It("identifies a member block", func() {
   214  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), memberKeyPair.Cert)
   215  			isMem, err := consenter.IsChannelMember(genesisBlockApp)
   216  			Expect(isMem).To(BeTrue())
   217  			Expect(err).NotTo(HaveOccurred())
   218  		})
   219  
   220  		It("identifies a non-member block", func() {
   221  			foreignCA, err := tlsgen.NewCA()
   222  			Expect(err).NotTo(HaveOccurred())
   223  			nonMemberKeyPair, err := foreignCA.NewServerCertKeyPair("127.0.0.1", "::1", "localhost")
   224  			Expect(err).NotTo(HaveOccurred())
   225  
   226  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), nonMemberKeyPair.Cert)
   227  			isMem, err := consenter.IsChannelMember(genesisBlockApp)
   228  			Expect(isMem).To(BeFalse())
   229  			Expect(err).NotTo(HaveOccurred())
   230  		})
   231  
   232  		It("raft config has consenter with certificate that is not signed by any msp", func() {
   233  			// This TLS root cert will not be part of the MSP.
   234  			foreignCA, err := tlsgen.NewCA()
   235  			Expect(err).NotTo(HaveOccurred())
   236  			foreignKeyPair, err := foreignCA.NewServerCertKeyPair("127.0.0.1", "::1", "localhost")
   237  			Expect(err).NotTo(HaveOccurred())
   238  
   239  			consenterDir, err := ioutil.TempDir(dataDir, "foreign-consenter")
   240  			Expect(err).NotTo(HaveOccurred())
   241  			foreignConsenterCertPath := filepath.Join(consenterDir, "client.pem")
   242  			err = ioutil.WriteFile(foreignConsenterCertPath, foreignKeyPair.Cert, 0o644)
   243  			Expect(err).NotTo(HaveOccurred())
   244  
   245  			confAppRaft.Orderer.EtcdRaft.Consenters[0].ClientTlsCert = []byte(foreignConsenterCertPath)
   246  			confAppRaft.Orderer.EtcdRaft.Consenters[0].ServerTlsCert = []byte(foreignConsenterCertPath)
   247  
   248  			consenter := newConsenter(chainManager, foreignCA.CertBytes(), foreignKeyPair.Cert)
   249  
   250  			bootstrapper, err := encoder.NewBootstrapper(confAppRaft)
   251  			Expect(err).NotTo(HaveOccurred())
   252  			genesisBlockApp = bootstrapper.GenesisBlockForChannel("my-raft-channel")
   253  			Expect(genesisBlockApp).NotTo(BeNil())
   254  
   255  			isMem, err := consenter.IsChannelMember(genesisBlockApp)
   256  			Expect(isMem).To(BeFalse())
   257  			Expect(err).To(HaveOccurred())
   258  		})
   259  	})
   260  
   261  	When("the consenter is asked for a chain", func() {
   262  		cryptoProvider, _ := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   263  		chainInstance := &etcdraft.Chain{CryptoProvider: cryptoProvider}
   264  		BeforeEach(func() {
   265  			chainManager.GetConsensusChainStub = func(channel string) consensus.Chain {
   266  				switch channel {
   267  				case "mychannel":
   268  					return chainInstance
   269  				case "notraftchain":
   270  					return &inactive.Chain{Err: errors.New("not a raft chain")}
   271  				default:
   272  					return nil
   273  				}
   274  			}
   275  		})
   276  
   277  		It("calls the chain manager and returns the reference when it is found", func() {
   278  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   279  			Expect(consenter).NotTo(BeNil())
   280  
   281  			chain := consenter.ReceiverByChain("mychannel")
   282  			Expect(chain).NotTo(BeNil())
   283  			Expect(chain).To(BeIdenticalTo(chainInstance))
   284  		})
   285  
   286  		It("calls the chain manager and returns nil when it's not found", func() {
   287  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   288  			Expect(consenter).NotTo(BeNil())
   289  
   290  			chain := consenter.ReceiverByChain("notmychannel")
   291  			Expect(chain).To(BeNil())
   292  		})
   293  
   294  		It("calls the chain manager and returns nil when it's not a raft chain", func() {
   295  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   296  			Expect(consenter).NotTo(BeNil())
   297  
   298  			chain := consenter.ReceiverByChain("notraftchain")
   299  			Expect(chain).To(BeNil())
   300  		})
   301  	})
   302  
   303  	It("successfully constructs a Chain", func() {
   304  		certAsPEMWithLineFeed := certAsPEM
   305  		certAsPEMWithLineFeed = append(certAsPEMWithLineFeed, []byte("\n")...)
   306  		m := &etcdraftproto.ConfigMetadata{
   307  			Consenters: []*etcdraftproto.Consenter{
   308  				{ServerTlsCert: certAsPEMWithLineFeed},
   309  			},
   310  			Options: &etcdraftproto.Options{
   311  				TickInterval:      "500ms",
   312  				ElectionTick:      10,
   313  				HeartbeatTick:     1,
   314  				MaxInflightBlocks: 5,
   315  			},
   316  		}
   317  		metadata := protoutil.MarshalOrPanic(m)
   318  		mockOrderer := &mocks.OrdererConfig{}
   319  		mockOrderer.ConsensusMetadataReturns(metadata)
   320  		mockOrderer.BatchSizeReturns(
   321  			&orderer.BatchSize{
   322  				PreferredMaxBytes: 2 * 1024 * 1024,
   323  			},
   324  		)
   325  		support.SharedConfigReturns(mockOrderer)
   326  
   327  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   328  		consenter.EtcdRaftConfig.WALDir = walDir
   329  		consenter.EtcdRaftConfig.SnapDir = snapDir
   330  		// consenter.EtcdRaftConfig.EvictionSuspicion is missing
   331  		var defaultSuspicionFallback bool
   332  		var trackChainCallback bool
   333  		consenter.Metrics = newFakeMetrics(newFakeMetricsFields())
   334  		consenter.Logger = consenter.Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
   335  			if strings.Contains(entry.Message, "EvictionSuspicion not set, defaulting to 10m0s") {
   336  				defaultSuspicionFallback = true
   337  			}
   338  			if strings.Contains(entry.Message, "With system channel: after eviction InactiveChainRegistry.TrackChain will be called") {
   339  				trackChainCallback = true
   340  			}
   341  			return nil
   342  		}))
   343  
   344  		chain, err := consenter.HandleChain(support, nil)
   345  		Expect(err).NotTo(HaveOccurred())
   346  		Expect(chain).NotTo(BeNil())
   347  
   348  		Expect(chain.Start).NotTo(Panic())
   349  		Expect(defaultSuspicionFallback).To(BeTrue())
   350  		Expect(trackChainCallback).To(BeTrue())
   351  	})
   352  
   353  	It("successfully constructs a Chain without a system channel", func() {
   354  		// We append a line feed to our cert, just to ensure that we can still consume it and ignore.
   355  		certAsPEMWithLineFeed := certAsPEM
   356  		certAsPEMWithLineFeed = append(certAsPEMWithLineFeed, []byte("\n")...)
   357  		m := &etcdraftproto.ConfigMetadata{
   358  			Consenters: []*etcdraftproto.Consenter{
   359  				{ServerTlsCert: certAsPEMWithLineFeed},
   360  			},
   361  			Options: &etcdraftproto.Options{
   362  				TickInterval:      "500ms",
   363  				ElectionTick:      10,
   364  				HeartbeatTick:     1,
   365  				MaxInflightBlocks: 5,
   366  			},
   367  		}
   368  		metadata := protoutil.MarshalOrPanic(m)
   369  		mockOrderer := &mocks.OrdererConfig{}
   370  		mockOrderer.ConsensusMetadataReturns(metadata)
   371  		mockOrderer.BatchSizeReturns(
   372  			&orderer.BatchSize{
   373  				PreferredMaxBytes: 2 * 1024 * 1024,
   374  			},
   375  		)
   376  		support.SharedConfigReturns(mockOrderer)
   377  
   378  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   379  		consenter.EtcdRaftConfig.WALDir = walDir
   380  		consenter.EtcdRaftConfig.SnapDir = snapDir
   381  		// without a system channel, the InactiveChainRegistry is nil
   382  		consenter.InactiveChainRegistry = nil
   383  		consenter.icr = nil
   384  
   385  		// consenter.EtcdRaftConfig.EvictionSuspicion is missing
   386  		var defaultSuspicionFallback bool
   387  		var switchToFollowerCallback bool
   388  		consenter.Metrics = newFakeMetrics(newFakeMetricsFields())
   389  		consenter.Logger = consenter.Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
   390  			if strings.Contains(entry.Message, "EvictionSuspicion not set, defaulting to 10m0s") {
   391  				defaultSuspicionFallback = true
   392  			}
   393  			if strings.Contains(entry.Message, "Without system channel: after eviction Registrar.SwitchToFollower will be called") {
   394  				switchToFollowerCallback = true
   395  			}
   396  			return nil
   397  		}))
   398  
   399  		chain, err := consenter.HandleChain(support, nil)
   400  		Expect(err).NotTo(HaveOccurred())
   401  		Expect(chain).NotTo(BeNil())
   402  
   403  		Expect(chain.Start).NotTo(Panic())
   404  		Expect(defaultSuspicionFallback).To(BeTrue())
   405  		Expect(switchToFollowerCallback).To(BeTrue())
   406  		Expect(chain.Halt).NotTo(Panic())
   407  	})
   408  
   409  	It("fails to handle chain if no matching cert found", func() {
   410  		m := &etcdraftproto.ConfigMetadata{
   411  			Consenters: []*etcdraftproto.Consenter{
   412  				{ServerTlsCert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("foo")})},
   413  			},
   414  			Options: &etcdraftproto.Options{
   415  				TickInterval:      "500ms",
   416  				ElectionTick:      10,
   417  				HeartbeatTick:     1,
   418  				MaxInflightBlocks: 5,
   419  			},
   420  		}
   421  		metadata := protoutil.MarshalOrPanic(m)
   422  		support := &consensusmocks.FakeConsenterSupport{}
   423  		mockOrderer := &mocks.OrdererConfig{}
   424  		mockOrderer.ConsensusMetadataReturns(metadata)
   425  		mockOrderer.BatchSizeReturns(
   426  			&orderer.BatchSize{
   427  				PreferredMaxBytes: 2 * 1024 * 1024,
   428  			},
   429  		)
   430  		support.SharedConfigReturns(mockOrderer)
   431  		support.ChannelIDReturns("foo")
   432  
   433  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   434  
   435  		chain, err := consenter.HandleChain(support, &common.Metadata{})
   436  		Expect(chain).To(Not(BeNil()))
   437  		Expect(err).To(Not(HaveOccurred()))
   438  		Expect(chain.Order(nil, 0).Error()).To(Equal("channel foo is not serviced by me"))
   439  		Expect(consenter.icr.TrackChainCallCount()).To(Equal(1))
   440  		Expect(chainManager.ReportConsensusRelationAndStatusMetricsCallCount()).To(Equal(1))
   441  		channel, relation, status := chainManager.ReportConsensusRelationAndStatusMetricsArgsForCall(0)
   442  		Expect(channel).To(Equal("foo"))
   443  		Expect(relation).To(Equal(types.ConsensusRelationConfigTracker))
   444  		Expect(status).To(Equal(types.StatusInactive))
   445  	})
   446  
   447  	It("fails to handle chain if etcdraft options have not been provided", func() {
   448  		m := &etcdraftproto.ConfigMetadata{
   449  			Consenters: []*etcdraftproto.Consenter{
   450  				{ServerTlsCert: []byte("cert.orderer1.org1")},
   451  			},
   452  		}
   453  		metadata := protoutil.MarshalOrPanic(m)
   454  		mockOrderer := &mocks.OrdererConfig{}
   455  		mockOrderer.ConsensusMetadataReturns(metadata)
   456  		mockOrderer.BatchSizeReturns(
   457  			&orderer.BatchSize{
   458  				PreferredMaxBytes: 2 * 1024 * 1024,
   459  			},
   460  		)
   461  		support.SharedConfigReturns(mockOrderer)
   462  
   463  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   464  
   465  		chain, err := consenter.HandleChain(support, nil)
   466  		Expect(chain).To(BeNil())
   467  		Expect(err).To(MatchError("etcdraft options have not been provided"))
   468  	})
   469  
   470  	It("fails to handle chain if tick interval is invalid", func() {
   471  		m := &etcdraftproto.ConfigMetadata{
   472  			Consenters: []*etcdraftproto.Consenter{
   473  				{ServerTlsCert: certAsPEM},
   474  			},
   475  			Options: &etcdraftproto.Options{
   476  				TickInterval:      "500",
   477  				ElectionTick:      10,
   478  				HeartbeatTick:     1,
   479  				MaxInflightBlocks: 5,
   480  			},
   481  		}
   482  		metadata := protoutil.MarshalOrPanic(m)
   483  		mockOrderer := &mocks.OrdererConfig{}
   484  		mockOrderer.ConsensusMetadataReturns(metadata)
   485  		mockOrderer.BatchSizeReturns(
   486  			&orderer.BatchSize{
   487  				PreferredMaxBytes: 2 * 1024 * 1024,
   488  			},
   489  		)
   490  		mockOrderer.CapabilitiesReturns(&mocks.OrdererCapabilities{})
   491  		support.SharedConfigReturns(mockOrderer)
   492  
   493  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   494  
   495  		chain, err := consenter.HandleChain(support, nil)
   496  		Expect(chain).To(BeNil())
   497  		Expect(err).To(MatchError("failed to parse TickInterval (500) to time duration"))
   498  	})
   499  
   500  	When("the TickIntervalOverride is invalid", func() {
   501  		It("returns an error", func() {
   502  			m := &etcdraftproto.ConfigMetadata{
   503  				Consenters: []*etcdraftproto.Consenter{
   504  					{ServerTlsCert: certAsPEM},
   505  				},
   506  				Options: &etcdraftproto.Options{
   507  					TickInterval:      "500",
   508  					ElectionTick:      10,
   509  					HeartbeatTick:     1,
   510  					MaxInflightBlocks: 5,
   511  				},
   512  			}
   513  			metadata := protoutil.MarshalOrPanic(m)
   514  			mockOrderer := &mocks.OrdererConfig{}
   515  			mockOrderer.ConsensusMetadataReturns(metadata)
   516  			mockOrderer.BatchSizeReturns(
   517  				&orderer.BatchSize{
   518  					PreferredMaxBytes: 2 * 1024 * 1024,
   519  				},
   520  			)
   521  			mockOrderer.CapabilitiesReturns(&mocks.OrdererCapabilities{})
   522  			support.SharedConfigReturns(mockOrderer)
   523  
   524  			consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   525  			consenter.EtcdRaftConfig.TickIntervalOverride = "seven"
   526  
   527  			_, err := consenter.HandleChain(support, nil)
   528  			Expect(err).To(MatchError(HavePrefix("failed parsing Consensus.TickIntervalOverride:")))
   529  			Expect(err).To(MatchError(ContainSubstring("seven")))
   530  		})
   531  	})
   532  
   533  	It("returns an error if no matching cert found", func() {
   534  		m := &etcdraftproto.ConfigMetadata{
   535  			Consenters: []*etcdraftproto.Consenter{
   536  				{ServerTlsCert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("foo")})},
   537  			},
   538  			Options: &etcdraftproto.Options{
   539  				TickInterval:      "500ms",
   540  				ElectionTick:      10,
   541  				HeartbeatTick:     1,
   542  				MaxInflightBlocks: 5,
   543  			},
   544  		}
   545  		metadata := protoutil.MarshalOrPanic(m)
   546  		support := &consensusmocks.FakeConsenterSupport{}
   547  		mockOrderer := &mocks.OrdererConfig{}
   548  		mockOrderer.ConsensusMetadataReturns(metadata)
   549  		mockOrderer.BatchSizeReturns(
   550  			&orderer.BatchSize{
   551  				PreferredMaxBytes: 2 * 1024 * 1024,
   552  			},
   553  		)
   554  		support.SharedConfigReturns(mockOrderer)
   555  		support.ChannelIDReturns("foo")
   556  
   557  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   558  		// without a system channel, the InactiveChainRegistry is nil
   559  		consenter.RemoveInactiveChainRegistry()
   560  
   561  		chain, err := consenter.HandleChain(support, &common.Metadata{})
   562  		Expect(chain).To((BeNil()))
   563  		Expect(err).To(MatchError("without a system channel, a follower should have been created: not in the channel"))
   564  	})
   565  
   566  	It("removes the inactive chain registry (and doesn't panic upon retries)", func() {
   567  		consenter := newConsenter(chainManager, tlsCA.CertBytes(), certAsPEM)
   568  
   569  		consenter.RemoveInactiveChainRegistry()
   570  		Expect(consenter.icr.StopCallCount()).To(Equal(1))
   571  		Expect(consenter.InactiveChainRegistry).To(BeNil())
   572  
   573  		consenter.RemoveInactiveChainRegistry()
   574  		Expect(consenter.icr.StopCallCount()).To(Equal(1))
   575  	})
   576  })
   577  
   578  type consenter struct {
   579  	*etcdraft.Consenter
   580  	icr *mocks.InactiveChainRegistry
   581  }
   582  
   583  func newConsenter(chainManager *mocks.ChainManager, caCert, cert []byte) *consenter {
   584  	communicator := &clustermocks.Communicator{}
   585  	communicator.On("Configure", mock.Anything, mock.Anything)
   586  	icr := &mocks.InactiveChainRegistry{}
   587  
   588  	cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   589  	Expect(err).NotTo(HaveOccurred())
   590  
   591  	c := &etcdraft.Consenter{
   592  		ChainManager:          chainManager,
   593  		InactiveChainRegistry: icr,
   594  		Communication:         communicator,
   595  		Cert:                  cert,
   596  		Logger:                flogging.MustGetLogger("test"),
   597  		Dispatcher: &etcdraft.Dispatcher{
   598  			Logger:        flogging.MustGetLogger("test"),
   599  			ChainSelector: &mocks.ReceiverGetter{},
   600  		},
   601  		Dialer: &cluster.PredicateDialer{
   602  			Config: comm.ClientConfig{
   603  				SecOpts: comm.SecureOptions{
   604  					Certificate: caCert,
   605  				},
   606  			},
   607  		},
   608  		BCCSP: cryptoProvider,
   609  	}
   610  	return &consenter{
   611  		Consenter: c,
   612  		icr:       icr,
   613  	}
   614  }