github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/orderer/consensus/etcdraft/consenter_test.go (about)

     1  /*
     2  Copyright IBM Corp. 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  	"strings"
    16  
    17  	"github.com/golang/protobuf/proto"
    18  	"github.com/hyperledger/fabric-protos-go/common"
    19  	"github.com/hyperledger/fabric-protos-go/orderer"
    20  	etcdraftproto "github.com/hyperledger/fabric-protos-go/orderer/etcdraft"
    21  	"github.com/osdi23p228/fabric/bccsp/sw"
    22  	"github.com/osdi23p228/fabric/common/channelconfig"
    23  	"github.com/osdi23p228/fabric/common/crypto/tlsgen"
    24  	"github.com/osdi23p228/fabric/common/flogging"
    25  	"github.com/osdi23p228/fabric/internal/configtxgen/genesisconfig"
    26  	"github.com/osdi23p228/fabric/internal/pkg/comm"
    27  	"github.com/osdi23p228/fabric/orderer/common/cluster"
    28  	clustermocks "github.com/osdi23p228/fabric/orderer/common/cluster/mocks"
    29  	"github.com/osdi23p228/fabric/orderer/common/multichannel"
    30  	"github.com/osdi23p228/fabric/orderer/consensus/etcdraft"
    31  	"github.com/osdi23p228/fabric/orderer/consensus/etcdraft/mocks"
    32  	"github.com/osdi23p228/fabric/orderer/consensus/follower"
    33  	consensusmocks "github.com/osdi23p228/fabric/orderer/consensus/mocks"
    34  	"github.com/osdi23p228/fabric/protoutil"
    35  	. "github.com/onsi/ginkgo"
    36  	. "github.com/onsi/gomega"
    37  	"github.com/stretchr/testify/mock"
    38  	"go.uber.org/zap"
    39  	"go.uber.org/zap/zapcore"
    40  )
    41  
    42  //These fixtures contain certificates for testing consenters.
    43  //In both folders certificates generated using tlsgen pkg, each certificate is singed by ca.pem inside corresponding folder.
    44  //Each cert has 10years expiration time (tlsgenCa.NewServerCertKeyPair("localhost")).
    45  
    46  //NOTE ONLY FOR GO 1.15+: prior to go1.15 tlsgen produced CA root cert without SubjectKeyId, which is not allowed by MSP validator.
    47  //In this test left tags @ONLY-GO1.15+ in places where fixtures can be replaced with tlsgen runtime generated certs once fabric moved to 1.15
    48  
    49  const (
    50  	consentersTestDataDir = "testdata/consenters_certs/"
    51  	ca1Dir                = consentersTestDataDir + "ca1"
    52  	ca2Dir                = consentersTestDataDir + "ca2"
    53  )
    54  
    55  var (
    56  	certAsPEM []byte
    57  )
    58  
    59  //go:generate counterfeiter -o mocks/orderer_capabilities.go --fake-name OrdererCapabilities . ordererCapabilities
    60  
    61  type ordererCapabilities interface {
    62  	channelconfig.OrdererCapabilities
    63  }
    64  
    65  //go:generate counterfeiter -o mocks/orderer_config.go --fake-name OrdererConfig . ordererConfig
    66  
    67  type ordererConfig interface {
    68  	channelconfig.Orderer
    69  }
    70  
    71  var _ = Describe("Consenter", func() {
    72  	var (
    73  		chainGetter *mocks.ChainGetter
    74  		support     *consensusmocks.FakeConsenterSupport
    75  		dataDir     string
    76  		snapDir     string
    77  		walDir      string
    78  		mspDir      string
    79  		tlsCA       tlsgen.CA
    80  	)
    81  
    82  	BeforeEach(func() {
    83  		var err error
    84  		tlsCA, err = tlsgen.NewCA()
    85  		Expect(err).NotTo(HaveOccurred())
    86  		kp, err := tlsCA.NewClientCertKeyPair()
    87  		Expect(err).NotTo(HaveOccurred())
    88  		if certAsPEM == nil {
    89  			certAsPEM = kp.Cert
    90  		}
    91  		chainGetter = &mocks.ChainGetter{}
    92  		support = &consensusmocks.FakeConsenterSupport{}
    93  		dataDir, err = ioutil.TempDir("", "snap-")
    94  		Expect(err).NotTo(HaveOccurred())
    95  		walDir = path.Join(dataDir, "wal-")
    96  		snapDir = path.Join(dataDir, "snap-")
    97  
    98  		blockBytes, err := ioutil.ReadFile("testdata/mychannel.block")
    99  		Expect(err).NotTo(HaveOccurred())
   100  
   101  		goodConfigBlock := &common.Block{}
   102  		proto.Unmarshal(blockBytes, goodConfigBlock)
   103  
   104  		lastBlock := &common.Block{
   105  			Header: &common.BlockHeader{
   106  				Number: 1,
   107  			},
   108  			Data: goodConfigBlock.Data,
   109  			Metadata: &common.BlockMetadata{
   110  				Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{
   111  					Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 0}),
   112  				})},
   113  			},
   114  		}
   115  
   116  		support.BlockReturns(lastBlock)
   117  	})
   118  
   119  	AfterEach(func() {
   120  		os.RemoveAll(dataDir)
   121  		os.RemoveAll(mspDir)
   122  	})
   123  
   124  	When("the consenter is extracting the channel", func() {
   125  		It("extracts successfully from step requests", func() {
   126  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   127  			ch := consenter.TargetChannel(&orderer.ConsensusRequest{Channel: "mychannel"})
   128  			Expect(ch).To(BeIdenticalTo("mychannel"))
   129  		})
   130  		It("extracts successfully from submit requests", func() {
   131  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   132  			ch := consenter.TargetChannel(&orderer.SubmitRequest{Channel: "mychannel"})
   133  			Expect(ch).To(BeIdenticalTo("mychannel"))
   134  		})
   135  		It("returns an empty string for the rest of the messages", func() {
   136  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   137  			ch := consenter.TargetChannel(&common.Block{})
   138  			Expect(ch).To(BeEmpty())
   139  		})
   140  	})
   141  
   142  	When("the consenter is asked for a chain", func() {
   143  		cryptoProvider, _ := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   144  		chainInstance := &etcdraft.Chain{CryptoProvider: cryptoProvider}
   145  		cs := &multichannel.ChainSupport{
   146  			Chain: chainInstance,
   147  			BCCSP: cryptoProvider,
   148  		}
   149  		BeforeEach(func() {
   150  			chainGetter.On("GetChain", "mychannel").Return(cs)
   151  			chainGetter.On("GetChain", "badChainObject").Return(&multichannel.ChainSupport{})
   152  			chainGetter.On("GetChain", "notmychannel").Return(nil)
   153  			chainGetter.On("GetChain", "notraftchain").Return(&multichannel.ChainSupport{
   154  				Chain: &multichannel.ChainSupport{},
   155  			})
   156  		})
   157  		It("calls the chain manager and returns the reference when it is found", func() {
   158  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   159  			Expect(consenter).NotTo(BeNil())
   160  
   161  			chain := consenter.ReceiverByChain("mychannel")
   162  			Expect(chain).NotTo(BeNil())
   163  			Expect(chain).To(BeIdenticalTo(chainInstance))
   164  		})
   165  		It("calls the chain manager and returns nil when it's not found", func() {
   166  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   167  			Expect(consenter).NotTo(BeNil())
   168  
   169  			chain := consenter.ReceiverByChain("notmychannel")
   170  			Expect(chain).To(BeNil())
   171  		})
   172  		It("calls the chain manager and returns nil when it's not a raft chain", func() {
   173  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   174  			Expect(consenter).NotTo(BeNil())
   175  
   176  			chain := consenter.ReceiverByChain("notraftchain")
   177  			Expect(chain).To(BeNil())
   178  		})
   179  		It("calls the chain getter and panics when the chain has a bad internal state", func() {
   180  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   181  			Expect(consenter).NotTo(BeNil())
   182  
   183  			Expect(func() {
   184  				consenter.ReceiverByChain("badChainObject")
   185  			}).To(Panic())
   186  		})
   187  	})
   188  
   189  	It("successfully constructs a Chain", func() {
   190  		certAsPEMWithLineFeed := certAsPEM
   191  		certAsPEMWithLineFeed = append(certAsPEMWithLineFeed, []byte("\n")...)
   192  		m := &etcdraftproto.ConfigMetadata{
   193  			Consenters: []*etcdraftproto.Consenter{
   194  				{ServerTlsCert: certAsPEMWithLineFeed},
   195  			},
   196  			Options: &etcdraftproto.Options{
   197  				TickInterval:      "500ms",
   198  				ElectionTick:      10,
   199  				HeartbeatTick:     1,
   200  				MaxInflightBlocks: 5,
   201  			},
   202  		}
   203  		metadata := protoutil.MarshalOrPanic(m)
   204  		mockOrderer := &mocks.OrdererConfig{}
   205  		mockOrderer.ConsensusMetadataReturns(metadata)
   206  		mockOrderer.BatchSizeReturns(
   207  			&orderer.BatchSize{
   208  				PreferredMaxBytes: 2 * 1024 * 1024,
   209  			},
   210  		)
   211  		support.SharedConfigReturns(mockOrderer)
   212  
   213  		consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   214  		consenter.EtcdRaftConfig.WALDir = walDir
   215  		consenter.EtcdRaftConfig.SnapDir = snapDir
   216  		// consenter.EtcdRaftConfig.EvictionSuspicion is missing
   217  		var defaultSuspicionFallback bool
   218  		consenter.Metrics = newFakeMetrics(newFakeMetricsFields())
   219  		consenter.Logger = consenter.Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
   220  			if strings.Contains(entry.Message, "EvictionSuspicion not set, defaulting to 10m0s") {
   221  				defaultSuspicionFallback = true
   222  			}
   223  			return nil
   224  		}))
   225  
   226  		chain, err := consenter.HandleChain(support, nil)
   227  		Expect(err).NotTo(HaveOccurred())
   228  		Expect(chain).NotTo(BeNil())
   229  
   230  		Expect(chain.Start).NotTo(Panic())
   231  		Expect(defaultSuspicionFallback).To(BeTrue())
   232  	})
   233  
   234  	It("successfully constructs a Chain without a system channel", func() {
   235  		// We append a line feed to our cert, just to ensure that we can still consume it and ignore.
   236  		certAsPEMWithLineFeed := certAsPEM
   237  		certAsPEMWithLineFeed = append(certAsPEMWithLineFeed, []byte("\n")...)
   238  		m := &etcdraftproto.ConfigMetadata{
   239  			Consenters: []*etcdraftproto.Consenter{
   240  				{ServerTlsCert: certAsPEMWithLineFeed},
   241  			},
   242  			Options: &etcdraftproto.Options{
   243  				TickInterval:      "500ms",
   244  				ElectionTick:      10,
   245  				HeartbeatTick:     1,
   246  				MaxInflightBlocks: 5,
   247  			},
   248  		}
   249  		metadata := protoutil.MarshalOrPanic(m)
   250  		mockOrderer := &mocks.OrdererConfig{}
   251  		mockOrderer.ConsensusMetadataReturns(metadata)
   252  		mockOrderer.BatchSizeReturns(
   253  			&orderer.BatchSize{
   254  				PreferredMaxBytes: 2 * 1024 * 1024,
   255  			},
   256  		)
   257  		support.SharedConfigReturns(mockOrderer)
   258  
   259  		consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   260  		consenter.EtcdRaftConfig.WALDir = walDir
   261  		consenter.EtcdRaftConfig.SnapDir = snapDir
   262  		//without a system channel, the InactiveChainRegistry is nil
   263  		consenter.InactiveChainRegistry = nil
   264  		consenter.icr = nil
   265  
   266  		// consenter.EtcdRaftConfig.EvictionSuspicion is missing
   267  		var defaultSuspicionFallback bool
   268  		consenter.Metrics = newFakeMetrics(newFakeMetricsFields())
   269  		consenter.Logger = consenter.Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
   270  			if strings.Contains(entry.Message, "EvictionSuspicion not set, defaulting to 10m0s") {
   271  				defaultSuspicionFallback = true
   272  			}
   273  			return nil
   274  		}))
   275  
   276  		chain, err := consenter.HandleChain(support, nil)
   277  		Expect(err).NotTo(HaveOccurred())
   278  		Expect(chain).NotTo(BeNil())
   279  
   280  		Expect(chain.Start).NotTo(Panic())
   281  		Expect(defaultSuspicionFallback).To(BeTrue())
   282  		Expect(chain.Halt).NotTo(Panic())
   283  	})
   284  
   285  	It("fails to handle chain if no matching cert found", func() {
   286  		m := &etcdraftproto.ConfigMetadata{
   287  			Consenters: []*etcdraftproto.Consenter{
   288  				{ServerTlsCert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("foo")})},
   289  			},
   290  			Options: &etcdraftproto.Options{
   291  				TickInterval:      "500ms",
   292  				ElectionTick:      10,
   293  				HeartbeatTick:     1,
   294  				MaxInflightBlocks: 5,
   295  			},
   296  		}
   297  		metadata := protoutil.MarshalOrPanic(m)
   298  		support := &consensusmocks.FakeConsenterSupport{}
   299  		mockOrderer := &mocks.OrdererConfig{}
   300  		mockOrderer.ConsensusMetadataReturns(metadata)
   301  		mockOrderer.BatchSizeReturns(
   302  			&orderer.BatchSize{
   303  				PreferredMaxBytes: 2 * 1024 * 1024,
   304  			},
   305  		)
   306  		support.SharedConfigReturns(mockOrderer)
   307  		support.ChannelIDReturns("foo")
   308  
   309  		consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   310  
   311  		chain, err := consenter.HandleChain(support, &common.Metadata{})
   312  		Expect(chain).To(Not(BeNil()))
   313  		Expect(err).To(Not(HaveOccurred()))
   314  		Expect(chain.Order(nil, 0).Error()).To(Equal("channel foo is not serviced by me"))
   315  		consenter.icr.AssertNumberOfCalls(testingInstance, "TrackChain", 1)
   316  	})
   317  
   318  	It("fails to handle chain if etcdraft options have not been provided", func() {
   319  		m := &etcdraftproto.ConfigMetadata{
   320  			Consenters: []*etcdraftproto.Consenter{
   321  				{ServerTlsCert: []byte("cert.orderer1.org1")},
   322  			},
   323  		}
   324  		metadata := protoutil.MarshalOrPanic(m)
   325  		mockOrderer := &mocks.OrdererConfig{}
   326  		mockOrderer.ConsensusMetadataReturns(metadata)
   327  		mockOrderer.BatchSizeReturns(
   328  			&orderer.BatchSize{
   329  				PreferredMaxBytes: 2 * 1024 * 1024,
   330  			},
   331  		)
   332  		support.SharedConfigReturns(mockOrderer)
   333  
   334  		consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   335  
   336  		chain, err := consenter.HandleChain(support, nil)
   337  		Expect(chain).To(BeNil())
   338  		Expect(err).To(MatchError("etcdraft options have not been provided"))
   339  	})
   340  
   341  	It("fails to handle chain if tick interval is invalid", func() {
   342  		m := &etcdraftproto.ConfigMetadata{
   343  			Consenters: []*etcdraftproto.Consenter{
   344  				{ServerTlsCert: certAsPEM},
   345  			},
   346  			Options: &etcdraftproto.Options{
   347  				TickInterval:      "500",
   348  				ElectionTick:      10,
   349  				HeartbeatTick:     1,
   350  				MaxInflightBlocks: 5,
   351  			},
   352  		}
   353  		metadata := protoutil.MarshalOrPanic(m)
   354  		mockOrderer := &mocks.OrdererConfig{}
   355  		mockOrderer.ConsensusMetadataReturns(metadata)
   356  		mockOrderer.BatchSizeReturns(
   357  			&orderer.BatchSize{
   358  				PreferredMaxBytes: 2 * 1024 * 1024,
   359  			},
   360  		)
   361  		mockOrderer.CapabilitiesReturns(&mocks.OrdererCapabilities{})
   362  		support.SharedConfigReturns(mockOrderer)
   363  
   364  		consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   365  
   366  		chain, err := consenter.HandleChain(support, nil)
   367  		Expect(chain).To(BeNil())
   368  		Expect(err).To(MatchError("failed to parse TickInterval (500) to time duration"))
   369  	})
   370  
   371  	When("the TickIntervalOverride is invalid", func() {
   372  		It("returns an error", func() {
   373  			m := &etcdraftproto.ConfigMetadata{
   374  				Consenters: []*etcdraftproto.Consenter{
   375  					{ServerTlsCert: certAsPEM},
   376  				},
   377  				Options: &etcdraftproto.Options{
   378  					TickInterval:      "500s",
   379  					ElectionTick:      10,
   380  					HeartbeatTick:     1,
   381  					MaxInflightBlocks: 5,
   382  				},
   383  			}
   384  			metadata := protoutil.MarshalOrPanic(m)
   385  			mockOrderer := &mocks.OrdererConfig{}
   386  			mockOrderer.ConsensusMetadataReturns(metadata)
   387  			mockOrderer.BatchSizeReturns(
   388  				&orderer.BatchSize{
   389  					PreferredMaxBytes: 2 * 1024 * 1024,
   390  				},
   391  			)
   392  			mockOrderer.CapabilitiesReturns(&mocks.OrdererCapabilities{})
   393  			support.SharedConfigReturns(mockOrderer)
   394  
   395  			consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   396  			consenter.EtcdRaftConfig.TickIntervalOverride = "seven"
   397  
   398  			_, err := consenter.HandleChain(support, nil)
   399  			Expect(err).To(MatchError("failed parsing Consensus.TickIntervalOverride: seven: time: invalid duration seven"))
   400  		})
   401  	})
   402  
   403  	It("constructs a follower chain if no matching cert found", func() {
   404  		m := &etcdraftproto.ConfigMetadata{
   405  			Consenters: []*etcdraftproto.Consenter{
   406  				{ServerTlsCert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("foo")})},
   407  			},
   408  			Options: &etcdraftproto.Options{
   409  				TickInterval:      "500ms",
   410  				ElectionTick:      10,
   411  				HeartbeatTick:     1,
   412  				MaxInflightBlocks: 5,
   413  			},
   414  		}
   415  		metadata := protoutil.MarshalOrPanic(m)
   416  		support := &consensusmocks.FakeConsenterSupport{}
   417  		mockOrderer := &mocks.OrdererConfig{}
   418  		mockOrderer.ConsensusMetadataReturns(metadata)
   419  		mockOrderer.BatchSizeReturns(
   420  			&orderer.BatchSize{
   421  				PreferredMaxBytes: 2 * 1024 * 1024,
   422  			},
   423  		)
   424  		support.SharedConfigReturns(mockOrderer)
   425  		support.ChannelIDReturns("foo")
   426  
   427  		consenter := newConsenter(chainGetter, tlsCA.CertBytes(), certAsPEM)
   428  		//without a system channel, the InactiveChainRegistry is nil
   429  		consenter.InactiveChainRegistry = nil
   430  		consenter.icr = nil
   431  
   432  		chain, err := consenter.HandleChain(support, &common.Metadata{})
   433  		Expect(chain).To(Not(BeNil()))
   434  		Expect(err).To(Not(HaveOccurred()))
   435  		Expect(chain.Order(nil, 0).Error()).To(Equal("orderer is a follower of channel foo"))
   436  		_, ok := chain.(*follower.Chain)
   437  		Expect(ok).To(BeTrue())
   438  	})
   439  })
   440  
   441  type consenter struct {
   442  	*etcdraft.Consenter
   443  	icr *mocks.InactiveChainRegistry
   444  }
   445  
   446  func newConsenter(chainGetter *mocks.ChainGetter, caCert, cert []byte) *consenter {
   447  	communicator := &clustermocks.Communicator{}
   448  	communicator.On("Configure", mock.Anything, mock.Anything)
   449  	icr := &mocks.InactiveChainRegistry{}
   450  	icr.On("TrackChain", "foo", mock.Anything, mock.Anything)
   451  
   452  	cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   453  	Expect(err).NotTo(HaveOccurred())
   454  
   455  	c := &etcdraft.Consenter{
   456  		InactiveChainRegistry: icr,
   457  		Communication:         communicator,
   458  		Cert:                  cert,
   459  		Logger:                flogging.MustGetLogger("test"),
   460  		Chains:                chainGetter,
   461  		Dispatcher: &etcdraft.Dispatcher{
   462  			Logger:        flogging.MustGetLogger("test"),
   463  			ChainSelector: &mocks.ReceiverGetter{},
   464  		},
   465  		Dialer: &cluster.PredicateDialer{
   466  			Config: comm.ClientConfig{
   467  				SecOpts: comm.SecureOptions{
   468  					Certificate: caCert,
   469  				},
   470  			},
   471  		},
   472  		BCCSP: cryptoProvider,
   473  	}
   474  	return &consenter{
   475  		Consenter: c,
   476  		icr:       icr,
   477  	}
   478  }
   479  
   480  func generateCertificates(confAppRaft *genesisconfig.Profile, tlsCA tlsgen.CA, certDir string) [][]byte {
   481  	certificates := [][]byte{}
   482  	for i, c := range confAppRaft.Orderer.EtcdRaft.Consenters {
   483  		srvC, err := tlsCA.NewServerCertKeyPair(c.Host)
   484  		Expect(err).NotTo(HaveOccurred())
   485  		srvP := path.Join(certDir, fmt.Sprintf("server%d.crt", i))
   486  		err = ioutil.WriteFile(srvP, srvC.Cert, 0644)
   487  		Expect(err).NotTo(HaveOccurred())
   488  
   489  		clnC, err := tlsCA.NewClientCertKeyPair()
   490  		Expect(err).NotTo(HaveOccurred())
   491  		clnP := path.Join(certDir, fmt.Sprintf("client%d.crt", i))
   492  		err = ioutil.WriteFile(clnP, clnC.Cert, 0644)
   493  		Expect(err).NotTo(HaveOccurred())
   494  
   495  		c.ServerTlsCert = []byte(srvP)
   496  		c.ClientTlsCert = []byte(clnP)
   497  
   498  		certificates = append(certificates, srvC.Cert)
   499  	}
   500  
   501  	return certificates
   502  }