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 }