github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/orderer/common/onboarding/onboarding_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package onboarding 8 9 import ( 10 "fmt" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "testing" 19 "time" 20 21 "github.com/golang/protobuf/proto" 22 "github.com/hyperledger/fabric-protos-go/common" 23 "github.com/hyperledger/fabric-protos-go/orderer" 24 "github.com/hyperledger/fabric-protos-go/orderer/etcdraft" 25 "github.com/osdi23p228/fabric/bccsp/sw" 26 "github.com/osdi23p228/fabric/common/channelconfig" 27 "github.com/osdi23p228/fabric/common/configtx" 28 deliver_mocks "github.com/osdi23p228/fabric/common/deliver/mock" 29 "github.com/osdi23p228/fabric/common/flogging" 30 "github.com/osdi23p228/fabric/common/ledger/blockledger" 31 "github.com/osdi23p228/fabric/core/config/configtest" 32 "github.com/osdi23p228/fabric/internal/configtxgen/encoder" 33 "github.com/osdi23p228/fabric/internal/configtxgen/genesisconfig" 34 "github.com/osdi23p228/fabric/internal/pkg/comm" 35 "github.com/osdi23p228/fabric/internal/pkg/identity" 36 "github.com/osdi23p228/fabric/orderer/common/cluster" 37 cluster_mocks "github.com/osdi23p228/fabric/orderer/common/cluster/mocks" 38 "github.com/osdi23p228/fabric/orderer/common/localconfig" 39 onboarding_mocks "github.com/osdi23p228/fabric/orderer/common/onboarding/mocks" 40 "github.com/osdi23p228/fabric/protoutil" 41 . "github.com/onsi/gomega" 42 "github.com/onsi/gomega/gbytes" 43 "github.com/onsi/gomega/gexec" 44 "github.com/pkg/errors" 45 "github.com/stretchr/testify/assert" 46 "github.com/stretchr/testify/mock" 47 "go.uber.org/zap" 48 "go.uber.org/zap/zapcore" 49 ) 50 51 // the paths to configtxgen and cryptogen, which can be used by tests to create 52 // genesis blocks and certificates 53 var configtxgen, cryptogen, tempDir string 54 55 func TestMain(m *testing.M) { 56 var err error 57 configtxgen, err = gexec.Build("github.com/osdi23p228/fabric/cmd/configtxgen") 58 if err != nil { 59 fmt.Fprintf(os.Stderr, "configtxgen build failed: %v", err) 60 os.Exit(-1) 61 } 62 cryptogen, err = gexec.Build("github.com/osdi23p228/fabric/cmd/cryptogen") 63 if err != nil { 64 fmt.Fprintf(os.Stderr, "cryptogen build failed: %v", err) 65 os.Exit(-1) 66 } 67 defer gexec.CleanupBuildArtifacts() 68 69 tempDir, err = ioutil.TempDir("", "onboarding-test") 70 if err != nil { 71 fmt.Fprintf(os.Stderr, "failed to create temporary directory: %v", err) 72 os.Exit(-1) 73 } 74 defer os.RemoveAll(tempDir) 75 76 copyYamlFiles("testdata", tempDir) 77 78 os.Exit(m.Run()) 79 } 80 81 //go:generate counterfeiter -o mocks/signer_serializer.go --fake-name SignerSerializer . signerSerializer 82 83 type signerSerializer interface { 84 identity.SignerSerializer 85 } 86 87 //go:generate counterfeiter -o mocks/read_writer.go --fake-name ReadWriter . readWriter 88 89 type readWriter interface{ blockledger.ReadWriter } 90 91 func copyYamlFiles(src, dst string) { 92 for _, file := range []string{"configtx.yaml", "examplecom-config.yaml"} { 93 fileBytes, err := ioutil.ReadFile(filepath.Join(src, file)) 94 if err != nil { 95 os.Exit(-1) 96 } 97 err = ioutil.WriteFile(filepath.Join(dst, file), fileBytes, 0644) 98 if err != nil { 99 os.Exit(-1) 100 } 101 } 102 } 103 104 func newServerNode(t *testing.T, key, cert []byte) *deliverServer { 105 srv, err := comm.NewGRPCServer("127.0.0.1:0", comm.ServerConfig{ 106 SecOpts: comm.SecureOptions{ 107 Key: key, 108 Certificate: cert, 109 UseTLS: true, 110 }, 111 }) 112 if err != nil { 113 panic(err) 114 } 115 ds := &deliverServer{ 116 t: t, 117 blockResponses: make(chan *orderer.DeliverResponse, 100), 118 srv: srv, 119 } 120 orderer.RegisterAtomicBroadcastServer(srv.Server(), ds) 121 go srv.Start() 122 return ds 123 } 124 125 type deliverServer struct { 126 isConnected int32 127 t *testing.T 128 srv *comm.GRPCServer 129 blockResponses chan *orderer.DeliverResponse 130 } 131 132 func (*deliverServer) Broadcast(orderer.AtomicBroadcast_BroadcastServer) error { 133 panic("implement me") 134 } 135 136 func (ds *deliverServer) Deliver(stream orderer.AtomicBroadcast_DeliverServer) error { 137 atomic.StoreInt32(&ds.isConnected, 1) 138 seekInfo, err := readSeekEnvelope(stream) 139 if err != nil { 140 panic(err) 141 } 142 if seekInfo.GetStart().GetSpecified() != nil { 143 return ds.deliverBlocks(stream) 144 } 145 if seekInfo.GetStart().GetNewest() != nil { 146 resp := <-ds.blockResponses 147 if resp == nil { 148 return nil 149 } 150 return stream.Send(resp) 151 } 152 panic(fmt.Sprintf("expected either specified or newest seek but got %v", seekInfo.GetStart())) 153 } 154 155 func readSeekEnvelope(stream orderer.AtomicBroadcast_DeliverServer) (*orderer.SeekInfo, error) { 156 env, err := stream.Recv() 157 if err != nil { 158 return nil, err 159 } 160 payload, err := protoutil.UnmarshalPayload(env.Payload) 161 if err != nil { 162 return nil, err 163 } 164 seekInfo := &orderer.SeekInfo{} 165 if err = proto.Unmarshal(payload.Data, seekInfo); err != nil { 166 return nil, err 167 } 168 return seekInfo, nil 169 } 170 171 func (ds *deliverServer) deliverBlocks(stream orderer.AtomicBroadcast_DeliverServer) error { 172 for { 173 blockChan := ds.blockResponses 174 response := <-blockChan 175 if response == nil { 176 return nil 177 } 178 if err := stream.Send(response); err != nil { 179 return err 180 } 181 } 182 } 183 184 func loadPEM(cryptoPath, suffix string, t *testing.T) []byte { 185 ordererTLSPath := filepath.Join(cryptoPath, "ordererOrganizations", "example.com", "orderers", "127.0.0.1.example.com", "tls") 186 b, err := ioutil.ReadFile(filepath.Join(ordererTLSPath, suffix)) 187 assert.NoError(t, err) 188 return b 189 } 190 191 func channelCreationBlock(systemChannel, applicationChannel string, prevBlock *common.Block) *common.Block { 192 block := &common.Block{ 193 Header: &common.BlockHeader{ 194 Number: prevBlock.Header.Number + 1, 195 PreviousHash: protoutil.BlockHeaderHash(prevBlock.Header), 196 }, 197 Metadata: &common.BlockMetadata{ 198 Metadata: [][]byte{{}, {}, {}, {}}, 199 }, 200 Data: &common.BlockData{ 201 Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ 202 Payload: protoutil.MarshalOrPanic(&common.Payload{ 203 Header: &common.Header{ 204 ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ 205 ChannelId: systemChannel, 206 Type: int32(common.HeaderType_ORDERER_TRANSACTION), 207 }), 208 }, 209 Data: protoutil.MarshalOrPanic(&common.Envelope{ 210 Payload: protoutil.MarshalOrPanic(&common.Payload{ 211 Header: &common.Header{ 212 ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ 213 Type: int32(common.HeaderType_CONFIG), 214 ChannelId: applicationChannel, 215 }), 216 }, 217 }), 218 }), 219 }), 220 })}, 221 }, 222 } 223 224 block.Header.DataHash = protoutil.BlockDataHash(block.Data) 225 return block 226 } 227 228 func TestOnboardingChannelUnavailable(t *testing.T) { 229 // Scenario: During the probing phase of the onboarding, 230 // a channel is deemed relevant and we try to pull it during the 231 // second phase, but alas - precisely at that time - it becomes 232 // unavailable. 233 // The onboarding code is expected to skip the replication after 234 // the maximum attempt number is exhausted, and not to try to replicate 235 // the channel indefinitely. 236 cryptoPath := generateCryptoMaterials(t, cryptogen) 237 defer os.RemoveAll(cryptoPath) 238 239 caCert := loadPEM(cryptoPath, "ca.crt", t) 240 key := loadPEM(cryptoPath, "server.key", t) 241 cert := loadPEM(cryptoPath, "server.crt", t) 242 243 deliverServer := newServerNode(t, key, cert) 244 defer deliverServer.srv.Stop() 245 246 systemChannelBlockPath := generateBootstrapBlock(t, tempDir, configtxgen, "system", "SampleSoloSystemChannel") 247 systemChannelBlockBytes, err := ioutil.ReadFile(systemChannelBlockPath) 248 assert.NoError(t, err) 249 250 applicationChannelBlockPath := generateBootstrapBlock(t, tempDir, configtxgen, "testchannel", "SampleOrgChannel") 251 applicationChannelBlockBytes, err := ioutil.ReadFile(applicationChannelBlockPath) 252 assert.NoError(t, err) 253 254 testchannelGB := &common.Block{} 255 assert.NoError(t, proto.Unmarshal(applicationChannelBlockBytes, testchannelGB)) 256 testchannelGB.Header.Number = 0 257 258 systemChannelGenesisBlock := &common.Block{ 259 Header: &common.BlockHeader{ 260 Number: 0, 261 }, 262 Metadata: &common.BlockMetadata{ 263 Metadata: [][]byte{{}, {}, {}}, 264 }, 265 Data: &common.BlockData{Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ 266 Payload: protoutil.MarshalOrPanic(&common.Payload{ 267 Header: &common.Header{}, 268 }), 269 })}}, 270 } 271 systemChannelGenesisBlock.Header.DataHash = protoutil.BlockDataHash(systemChannelGenesisBlock.Data) 272 273 channelCreationBlock := channelCreationBlock("system", "testchannel", systemChannelGenesisBlock) 274 275 bootBlock := &common.Block{} 276 assert.NoError(t, proto.Unmarshal(systemChannelBlockBytes, bootBlock)) 277 bootBlock.Header.Number = 2 278 bootBlock.Header.PreviousHash = protoutil.BlockHeaderHash(channelCreationBlock.Header) 279 injectOrdererEndpoint(t, bootBlock, deliverServer.srv.Address()) 280 injectConsenterCertificate(t, testchannelGB, cert) 281 282 blocksCommittedToSystemLedger := make(chan uint64, 3) 283 blocksCommittedToApplicationLedger := make(chan uint64, 1) 284 285 systemLedger := &cluster_mocks.LedgerWriter{} 286 systemLedger.On("Height").Return(uint64(0)) 287 systemLedger.On("Append", mock.Anything).Return(nil).Run(func(arguments mock.Arguments) { 288 seq := arguments.Get(0).(*common.Block).Header.Number 289 blocksCommittedToSystemLedger <- seq 290 }) 291 292 appLedger := &cluster_mocks.LedgerWriter{} 293 appLedger.On("Height").Return(uint64(0)) 294 appLedger.On("Append", mock.Anything).Return(nil).Run(func(arguments mock.Arguments) { 295 seq := arguments.Get(0).(*common.Block).Header.Number 296 blocksCommittedToApplicationLedger <- seq 297 }) 298 299 lf := &cluster_mocks.LedgerFactory{} 300 lf.On("GetOrCreate", "system").Return(systemLedger, nil) 301 lf.On("GetOrCreate", "testchannel").Return(appLedger, nil) 302 lf.On("Close") 303 304 config := &localconfig.TopLevel{ 305 General: localconfig.General{ 306 Cluster: localconfig.Cluster{ 307 ReplicationPullTimeout: time.Hour, 308 DialTimeout: time.Hour, 309 RPCTimeout: time.Hour, 310 ReplicationRetryTimeout: time.Millisecond, 311 ReplicationBufferSize: 1, 312 ReplicationMaxRetries: 5, 313 }, 314 }, 315 } 316 317 secConfig := comm.SecureOptions{ 318 Certificate: cert, 319 Key: key, 320 UseTLS: true, 321 ServerRootCAs: [][]byte{caCert}, 322 } 323 324 verifier := &cluster_mocks.BlockVerifier{} 325 verifier.On("VerifyBlockSignature", mock.Anything, mock.Anything).Return(nil) 326 vr := &cluster_mocks.VerifierRetriever{} 327 vr.On("RetrieveVerifier", mock.Anything).Return(verifier) 328 329 cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) 330 assert.NoError(t, err) 331 332 r := &ReplicationInitiator{ 333 verifierRetriever: vr, 334 lf: lf, 335 logger: flogging.MustGetLogger("testOnboarding"), 336 conf: config, 337 secOpts: secConfig, 338 cryptoProvider: cryptoProvider, 339 } 340 341 type event struct { 342 expectedLog string 343 responseFunc func(chan *orderer.DeliverResponse) 344 } 345 346 var probe, pullSystemChannel, pullAppChannel, failedPulling bool 347 348 var loggerHooks []zap.Option 349 350 for _, e := range []event{ 351 { 352 expectedLog: "Probing whether I should pull channel testchannel", 353 responseFunc: func(blockResponses chan *orderer.DeliverResponse) { 354 probe = true 355 356 // At this point the client will re-connect, so close the stream. 357 blockResponses <- nil 358 // And send the genesis block of the application channel 'testchannel' 359 blockResponses <- &orderer.DeliverResponse{ 360 Type: &orderer.DeliverResponse_Block{ 361 Block: testchannelGB, 362 }, 363 } 364 blockResponses <- &orderer.DeliverResponse{ 365 Type: &orderer.DeliverResponse_Block{ 366 Block: testchannelGB, 367 }, 368 } 369 blockResponses <- &orderer.DeliverResponse{ 370 Type: &orderer.DeliverResponse_Block{ 371 Block: testchannelGB, 372 }, 373 } 374 blockResponses <- nil 375 blockResponses <- &orderer.DeliverResponse{ 376 Type: &orderer.DeliverResponse_Block{ 377 Block: testchannelGB, 378 }, 379 } 380 blockResponses <- &orderer.DeliverResponse{ 381 Type: &orderer.DeliverResponse_Block{ 382 Block: testchannelGB, 383 }, 384 } 385 blockResponses <- nil 386 }, 387 }, 388 { 389 expectedLog: "Pulling channel system", 390 responseFunc: func(blockResponses chan *orderer.DeliverResponse) { 391 pullSystemChannel = true 392 393 blockResponses <- &orderer.DeliverResponse{ 394 Type: &orderer.DeliverResponse_Block{ 395 Block: bootBlock, 396 }, 397 } 398 blockResponses <- &orderer.DeliverResponse{ 399 Type: &orderer.DeliverResponse_Block{ 400 Block: bootBlock, 401 }, 402 } 403 blockResponses <- &orderer.DeliverResponse{ 404 Type: &orderer.DeliverResponse_Block{ 405 Block: systemChannelGenesisBlock, 406 }, 407 } 408 blockResponses <- &orderer.DeliverResponse{ 409 Type: &orderer.DeliverResponse_Block{ 410 Block: channelCreationBlock, 411 }, 412 } 413 blockResponses <- &orderer.DeliverResponse{ 414 Type: &orderer.DeliverResponse_Block{ 415 Block: bootBlock, 416 }, 417 } 418 }, 419 }, 420 { 421 expectedLog: "Pulling channel testchannel", 422 responseFunc: func(blockResponses chan *orderer.DeliverResponse) { 423 pullAppChannel = true 424 425 for i := 0; i < config.General.Cluster.ReplicationMaxRetries+1; i++ { 426 // Send once the genesis block, to make the client think this is a valid OSN endpoint 427 deliverServer.blockResponses <- &orderer.DeliverResponse{ 428 Type: &orderer.DeliverResponse_Block{ 429 Block: testchannelGB, 430 }, 431 } 432 // Send EOF to make the client abort and retry again 433 deliverServer.blockResponses <- nil 434 } 435 }, 436 }, 437 { 438 expectedLog: "Failed pulling channel testchannel: retry attempts exhausted", 439 responseFunc: func(blockResponses chan *orderer.DeliverResponse) { 440 failedPulling = true 441 }, 442 }, 443 } { 444 event := e 445 loggerHooks = append(loggerHooks, zap.Hooks(func(entry zapcore.Entry) error { 446 if strings.Contains(entry.Message, event.expectedLog) { 447 event.responseFunc(deliverServer.blockResponses) 448 } 449 return nil 450 })) 451 } 452 453 // Program the logger to intercept the event 454 r.logger = r.logger.WithOptions(loggerHooks...) 455 456 // Send the latest system channel block (sequence 2) that is identical to the bootstrap block 457 deliverServer.blockResponses <- &orderer.DeliverResponse{ 458 Type: &orderer.DeliverResponse_Block{ 459 Block: bootBlock, 460 }, 461 } 462 // Send the bootstrap block of the system channel 463 deliverServer.blockResponses <- &orderer.DeliverResponse{ 464 Type: &orderer.DeliverResponse_Block{ 465 Block: systemChannelGenesisBlock, 466 }, 467 } 468 // Send a channel creation block (sequence 1) that denotes creation of 'testchannel' 469 deliverServer.blockResponses <- &orderer.DeliverResponse{ 470 Type: &orderer.DeliverResponse_Block{ 471 Block: channelCreationBlock, 472 }, 473 } 474 475 r.ReplicateIfNeeded(bootBlock) 476 477 // Ensure all events were invoked 478 assert.True(t, probe) 479 assert.True(t, pullSystemChannel) 480 assert.True(t, pullAppChannel) 481 assert.True(t, failedPulling) 482 483 // Ensure system channel was fully pulled 484 assert.Len(t, blocksCommittedToSystemLedger, 3) 485 // But the application channel only contains 1 block (the genesis block) 486 assert.Len(t, blocksCommittedToApplicationLedger, 1) 487 } 488 489 func TestReplicate(t *testing.T) { 490 clusterConfig := localconfig.Cluster{ 491 ReplicationPullTimeout: time.Hour, 492 DialTimeout: time.Hour, 493 RPCTimeout: time.Hour, 494 ReplicationRetryTimeout: time.Hour, 495 ReplicationBufferSize: 1, 496 } 497 498 var bootBlock common.Block 499 var bootBlockWithCorruptedPayload common.Block 500 501 flogging.ActivateSpec("testReplicateIfNeeded=debug") 502 503 cleanup := configtest.SetDevFabricConfigPath(t) 504 defer cleanup() 505 506 cryptoPath := generateCryptoMaterials(t, cryptogen) 507 defer os.RemoveAll(cryptoPath) 508 509 applicationChannelBlockPath := generateBootstrapBlock(t, tempDir, configtxgen, "testchannel", "SampleOrgChannel") 510 applicationChannelBlockBytes, err := ioutil.ReadFile(applicationChannelBlockPath) 511 assert.NoError(t, err) 512 513 caCert := loadPEM(cryptoPath, "ca.crt", t) 514 key := loadPEM(cryptoPath, "server.key", t) 515 cert := loadPEM(cryptoPath, "server.crt", t) 516 517 prepareTestCase := func() *deliverServer { 518 deliverServer := newServerNode(t, key, cert) 519 520 assert.NoError(t, proto.Unmarshal(applicationChannelBlockBytes, &bootBlock)) 521 bootBlock.Header.Number = 10 522 injectOrdererEndpoint(t, &bootBlock, deliverServer.srv.Address()) 523 524 copyBlock := func(block *common.Block, seq uint64) common.Block { 525 res := common.Block{} 526 assert.NoError(t, proto.Unmarshal(protoutil.MarshalOrPanic(block), &res)) 527 res.Header.Number = seq 528 return res 529 } 530 531 bootBlockWithCorruptedPayload = copyBlock(&bootBlock, 100) 532 env := &common.Envelope{} 533 assert.NoError(t, proto.Unmarshal(bootBlockWithCorruptedPayload.Data.Data[0], env)) 534 payload := &common.Payload{} 535 assert.NoError(t, proto.Unmarshal(env.Payload, payload)) 536 payload.Data = []byte{1, 2, 3} 537 538 deliverServer.blockResponses <- &orderer.DeliverResponse{ 539 Type: &orderer.DeliverResponse_Block{Block: &bootBlock}, 540 } 541 542 blocks := make([]*common.Block, 11) 543 for seq := uint64(0); seq <= uint64(10); seq++ { 544 block := copyBlock(&bootBlock, seq) 545 if seq > 0 { 546 block.Header.PreviousHash = protoutil.BlockHeaderHash(blocks[seq-1].Header) 547 } 548 blocks[seq] = &block 549 deliverServer.blockResponses <- &orderer.DeliverResponse{ 550 Type: &orderer.DeliverResponse_Block{Block: &block}, 551 } 552 } 553 // We close the block responses to mark the server side to return from 554 // the method dispatch. 555 close(deliverServer.blockResponses) 556 557 // We need to ensure the hash chain is valid with respect to the bootstrap block. 558 // Validating the hash chain itself when we traverse channels will be taken care 559 // of in FAB-12926. 560 bootBlock.Header.PreviousHash = protoutil.BlockHeaderHash(blocks[9].Header) 561 return deliverServer 562 } 563 564 var hooksActivated bool 565 566 for _, testCase := range []struct { 567 name string 568 panicValue string 569 systemLedgerHeight uint64 570 bootBlock *common.Block 571 secOpts comm.SecureOptions 572 conf *localconfig.TopLevel 573 ledgerFactoryErr error 574 signer identity.SignerSerializer 575 zapHooks []func(zapcore.Entry) error 576 shouldConnect bool 577 replicateFunc func(*ReplicationInitiator, *common.Block) 578 verificationCount int 579 }{ 580 { 581 name: "Genesis block makes replication be skipped", 582 bootBlock: &common.Block{Header: &common.BlockHeader{Number: 0}}, 583 systemLedgerHeight: 10, 584 zapHooks: []func(entry zapcore.Entry) error{ 585 func(entry zapcore.Entry) error { 586 hooksActivated = true 587 assert.Equal(t, entry.Message, "Booted with a genesis block, replication isn't an option") 588 return nil 589 }, 590 }, 591 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 592 ri.ReplicateIfNeeded(bootstrapBlock) 593 }, 594 }, 595 { 596 name: "Block puller initialization failure panics", 597 systemLedgerHeight: 10, 598 panicValue: "Failed creating puller config from bootstrap block: unable to decode TLS certificate PEM: ", 599 bootBlock: &bootBlockWithCorruptedPayload, 600 conf: &localconfig.TopLevel{}, 601 secOpts: comm.SecureOptions{}, 602 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 603 ri.ReplicateIfNeeded(bootstrapBlock) 604 }, 605 }, 606 { 607 name: "Extraction of system channel name fails", 608 systemLedgerHeight: 10, 609 panicValue: "Failed extracting system channel name from bootstrap block: failed to retrieve channel id - block is empty", 610 bootBlock: &common.Block{Header: &common.BlockHeader{Number: 100}}, 611 conf: &localconfig.TopLevel{}, 612 secOpts: comm.SecureOptions{}, 613 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 614 ri.ReplicateIfNeeded(bootstrapBlock) 615 }, 616 }, 617 { 618 name: "Is Replication needed fails", 619 systemLedgerHeight: 10, 620 ledgerFactoryErr: errors.New("I/O error"), 621 panicValue: "Failed determining whether replication is needed: I/O error", 622 bootBlock: &bootBlock, 623 conf: &localconfig.TopLevel{}, 624 secOpts: comm.SecureOptions{ 625 Certificate: cert, 626 Key: key, 627 }, 628 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 629 ri.ReplicateIfNeeded(bootstrapBlock) 630 }, 631 }, 632 { 633 name: "Replication isn't needed", 634 systemLedgerHeight: 11, 635 bootBlock: &bootBlock, 636 conf: &localconfig.TopLevel{}, 637 secOpts: comm.SecureOptions{ 638 Certificate: cert, 639 Key: key, 640 }, 641 zapHooks: []func(entry zapcore.Entry) error{ 642 func(entry zapcore.Entry) error { 643 hooksActivated = true 644 assert.Equal(t, entry.Message, "Replication isn't needed") 645 return nil 646 }, 647 }, 648 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 649 ri.ReplicateIfNeeded(bootstrapBlock) 650 }, 651 }, 652 { 653 name: "Replication is needed, but pulling fails", 654 panicValue: "Failed pulling system channel: " + 655 "failed obtaining the latest block for channel testchannel", 656 shouldConnect: true, 657 systemLedgerHeight: 10, 658 bootBlock: &bootBlock, 659 conf: &localconfig.TopLevel{ 660 General: localconfig.General{ 661 Cluster: clusterConfig, 662 }, 663 }, 664 secOpts: comm.SecureOptions{ 665 Certificate: cert, 666 Key: key, 667 UseTLS: true, 668 ServerRootCAs: [][]byte{caCert}, 669 }, 670 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 671 ri.ReplicateIfNeeded(bootstrapBlock) 672 }, 673 }, 674 { 675 name: "Explicit replication is requested, but the channel shouldn't be pulled", 676 verificationCount: 10, 677 shouldConnect: true, 678 systemLedgerHeight: 10, 679 bootBlock: &bootBlock, 680 conf: &localconfig.TopLevel{ 681 General: localconfig.General{ 682 Cluster: clusterConfig, 683 }, 684 }, 685 secOpts: comm.SecureOptions{ 686 Certificate: cert, 687 Key: key, 688 UseTLS: true, 689 ServerRootCAs: [][]byte{caCert}, 690 }, 691 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 692 ri.ReplicateChains(bootstrapBlock, []string{"foo"}) 693 }, 694 zapHooks: []func(entry zapcore.Entry) error{ 695 func(entry zapcore.Entry) error { 696 possibleLogs := []string{ 697 "Will now replicate chains [foo]", 698 "Channel testchannel shouldn't be pulled. Skipping it", 699 } 700 for _, possibleLog := range possibleLogs { 701 if entry.Message == possibleLog { 702 hooksActivated = true 703 } 704 } 705 return nil 706 }, 707 }, 708 }, 709 { 710 name: "Explicit replication is requested, but the channel cannot be pulled", 711 panicValue: "Failed pulling system channel: " + 712 "failed obtaining the latest block for channel testchannel", 713 shouldConnect: true, 714 systemLedgerHeight: 10, 715 bootBlock: &bootBlock, 716 conf: &localconfig.TopLevel{ 717 General: localconfig.General{ 718 Cluster: clusterConfig, 719 }, 720 }, 721 secOpts: comm.SecureOptions{ 722 Certificate: cert, 723 Key: key, 724 UseTLS: true, 725 ServerRootCAs: [][]byte{caCert}, 726 }, 727 replicateFunc: func(ri *ReplicationInitiator, bootstrapBlock *common.Block) { 728 ri.ReplicateChains(bootstrapBlock, []string{"testchannel"}) 729 }, 730 }, 731 } { 732 testCase := testCase 733 t.Run(testCase.name, func(t *testing.T) { 734 deliverServer := prepareTestCase() 735 defer deliverServer.srv.Stop() 736 737 lw := &cluster_mocks.LedgerWriter{} 738 lw.On("Height").Return(testCase.systemLedgerHeight).Once() 739 740 lf := &cluster_mocks.LedgerFactory{} 741 lf.On("GetOrCreate", mock.Anything).Return(lw, testCase.ledgerFactoryErr).Twice() 742 lf.On("Close") 743 744 verifier := &cluster_mocks.BlockVerifier{} 745 verifier.On("VerifyBlockSignature", mock.Anything, mock.Anything).Return(nil) 746 vr := &cluster_mocks.VerifierRetriever{} 747 vr.On("RetrieveVerifier", mock.Anything).Return(verifier) 748 749 cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) 750 assert.NoError(t, err) 751 752 r := &ReplicationInitiator{ 753 verifierRetriever: vr, 754 lf: lf, 755 logger: flogging.MustGetLogger("testReplicateIfNeeded"), 756 signer: testCase.signer, 757 758 conf: testCase.conf, 759 secOpts: testCase.secOpts, 760 cryptoProvider: cryptoProvider, 761 } 762 763 if testCase.panicValue != "" { 764 assert.PanicsWithValue(t, testCase.panicValue, func() { 765 testCase.replicateFunc(r, testCase.bootBlock) 766 }) 767 return 768 } 769 770 // Else, we are not expected to panic. 771 r.logger = r.logger.WithOptions(zap.Hooks(testCase.zapHooks...)) 772 773 // This is the method we're testing. 774 testCase.replicateFunc(r, testCase.bootBlock) 775 776 // Ensure we ran the hooks for a test case that doesn't panic 777 assert.True(t, hooksActivated) 778 // Restore the flag for the next iteration 779 defer func() { 780 hooksActivated = false 781 }() 782 783 assert.Equal(t, testCase.shouldConnect, atomic.LoadInt32(&deliverServer.isConnected) == int32(1)) 784 verifier.AssertNumberOfCalls(t, "VerifyBlockSignature", testCase.verificationCount) 785 }) 786 } 787 } 788 789 func TestInactiveChainReplicator(t *testing.T) { 790 for _, testCase := range []struct { 791 description string 792 chainsTracked []string 793 ReplicateChainsExpectedInput1 []string 794 ReplicateChainsExpectedInput1Reverse []string 795 ReplicateChainsExpectedInput2 []string 796 ReplicateChainsOutput1 []string 797 ReplicateChainsOutput2 []string 798 chainsExpectedToBeReplicated []string 799 expectedRegisteredChains map[string]struct{} 800 ReplicateChainsExpectedCallCount int 801 genesisBlock *common.Block 802 }{ 803 { 804 description: "no chains tracked", 805 expectedRegisteredChains: map[string]struct{}{}, 806 }, 807 { 808 description: "some chains tracked, but not all succeed replication", 809 chainsTracked: []string{"foo", "bar"}, 810 ReplicateChainsExpectedInput1: []string{"foo", "bar"}, 811 ReplicateChainsExpectedInput1Reverse: []string{"bar", "foo"}, 812 ReplicateChainsExpectedInput2: []string{"bar"}, 813 ReplicateChainsOutput1: []string{"foo"}, 814 ReplicateChainsOutput2: []string{}, 815 chainsExpectedToBeReplicated: []string{"foo"}, 816 ReplicateChainsExpectedCallCount: 2, 817 genesisBlock: &common.Block{}, 818 expectedRegisteredChains: map[string]struct{}{ 819 "foo": {}, 820 "bar": {}, 821 }, 822 }, 823 { 824 description: "some chains tracked, and all succeed replication but on 2nd pass", 825 chainsTracked: []string{"foo", "bar"}, 826 ReplicateChainsExpectedInput1: []string{"foo", "bar"}, 827 ReplicateChainsExpectedInput1Reverse: []string{"bar", "foo"}, 828 ReplicateChainsExpectedInput2: []string{"bar"}, 829 ReplicateChainsOutput1: []string{"foo"}, 830 ReplicateChainsOutput2: []string{"bar"}, 831 chainsExpectedToBeReplicated: []string{"foo", "bar"}, 832 ReplicateChainsExpectedCallCount: 2, 833 genesisBlock: &common.Block{}, 834 expectedRegisteredChains: map[string]struct{}{ 835 "foo": {}, 836 "bar": {}, 837 }, 838 }, 839 } { 840 t.Run(testCase.description, func(t *testing.T) { 841 registeredChains := make(map[string]struct{}) 842 registerChain := func(chain string) { 843 registeredChains[chain] = struct{}{} 844 } 845 scheduler := make(chan time.Time) 846 replicator := &onboarding_mocks.ChainReplicator{} 847 icr := &InactiveChainReplicator{ 848 registerChain: registerChain, 849 logger: flogging.MustGetLogger("test"), 850 replicator: replicator, 851 chains2CreationCallbacks: make(map[string]chainCreation), 852 retrieveLastSysChannelConfigBlock: func() *common.Block { 853 return nil 854 }, 855 quitChan: make(chan struct{}), 856 scheduleChan: scheduler, 857 } 858 859 trackedChains := make(chan string, 10) 860 // Simulate starting of a chain by simply adding it to a channel 861 for _, trackedChain := range testCase.chainsTracked { 862 trackedChain := trackedChain 863 icr.TrackChain(trackedChain, testCase.genesisBlock, func() { 864 trackedChains <- trackedChain 865 }) 866 } 867 868 // First pass 869 input := testCase.ReplicateChainsExpectedInput1 870 output := testCase.ReplicateChainsOutput1 871 replicator.On("ReplicateChains", mock.Anything, input).Return(output).Once() 872 // the input might be called in reverse order 873 input = testCase.ReplicateChainsExpectedInput1Reverse 874 replicator.On("ReplicateChains", mock.Anything, input).Return(output).Once() 875 876 // Second pass 877 input = testCase.ReplicateChainsExpectedInput2 878 output = testCase.ReplicateChainsOutput2 879 replicator.On("ReplicateChains", mock.Anything, input).Return(output).Once() 880 881 var replicatorStopped sync.WaitGroup 882 replicatorStopped.Add(1) 883 go func() { 884 defer replicatorStopped.Done() 885 // trigger to replicate the first time 886 scheduler <- time.Time{} 887 // trigger to replicate a second time 888 scheduler <- time.Time{} 889 icr.stop() 890 }() 891 icr.Run() 892 replicatorStopped.Wait() 893 close(trackedChains) 894 895 var replicatedChains []string 896 for chain := range trackedChains { 897 replicatedChains = append(replicatedChains, chain) 898 } 899 assert.Equal(t, testCase.chainsExpectedToBeReplicated, replicatedChains) 900 replicator.AssertNumberOfCalls(t, "ReplicateChains", testCase.ReplicateChainsExpectedCallCount) 901 assert.Equal(t, testCase.expectedRegisteredChains, registeredChains) 902 }) 903 } 904 } 905 906 func TestInactiveChainReplicatorChannels(t *testing.T) { 907 icr := &InactiveChainReplicator{ 908 logger: flogging.MustGetLogger("test"), 909 chains2CreationCallbacks: make(map[string]chainCreation), 910 } 911 icr.TrackChain("foo", &common.Block{}, func() {}) 912 assert.Contains(t, icr.Channels(), cluster.ChannelGenesisBlock{ChannelName: "foo", GenesisBlock: &common.Block{}}) 913 914 icr.TrackChain("bar", nil, func() {}) 915 assert.Contains(t, icr.Channels(), cluster.ChannelGenesisBlock{ChannelName: "bar", GenesisBlock: nil}) 916 917 icr.Close() 918 } 919 920 func injectConsenterCertificate(t *testing.T, block *common.Block, tlsCert []byte) { 921 env, err := protoutil.ExtractEnvelope(block, 0) 922 assert.NoError(t, err) 923 payload, err := protoutil.UnmarshalPayload(env.Payload) 924 assert.NoError(t, err) 925 confEnv, err := configtx.UnmarshalConfigEnvelope(payload.Data) 926 assert.NoError(t, err) 927 consensus := confEnv.Config.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Values[channelconfig.ConsensusTypeKey] 928 consensus.Value = protoutil.MarshalOrPanic(&orderer.ConsensusType{ 929 Type: "etcdraft", 930 Metadata: protoutil.MarshalOrPanic(&etcdraft.ConfigMetadata{ 931 Consenters: []*etcdraft.Consenter{ 932 { 933 ServerTlsCert: tlsCert, 934 ClientTlsCert: tlsCert, 935 }, 936 }, 937 }), 938 }) 939 940 payload.Data = protoutil.MarshalOrPanic(confEnv) 941 env.Payload = protoutil.MarshalOrPanic(payload) 942 block.Data.Data[0] = protoutil.MarshalOrPanic(env) 943 block.Header.DataHash = protoutil.BlockDataHash(block.Data) 944 } 945 946 func injectOrdererEndpoint(t *testing.T, block *common.Block, endpoint string) { 947 ordererAddresses := channelconfig.OrdererAddressesValue([]string{endpoint}) 948 // Unwrap the layers until we reach the orderer addresses 949 env, err := protoutil.ExtractEnvelope(block, 0) 950 assert.NoError(t, err) 951 payload, err := protoutil.UnmarshalPayload(env.Payload) 952 assert.NoError(t, err) 953 confEnv, err := configtx.UnmarshalConfigEnvelope(payload.Data) 954 assert.NoError(t, err) 955 // Replace the orderer addresses 956 confEnv.Config.ChannelGroup.Values[ordererAddresses.Key()].Value = protoutil.MarshalOrPanic(ordererAddresses.Value()) 957 // And put it back into the block 958 payload.Data = protoutil.MarshalOrPanic(confEnv) 959 env.Payload = protoutil.MarshalOrPanic(payload) 960 block.Data.Data[0] = protoutil.MarshalOrPanic(env) 961 block.Header.DataHash = protoutil.BlockDataHash(block.Data) 962 } 963 964 func TestVerifierLoader(t *testing.T) { 965 cryptoPath := generateCryptoMaterials(t, cryptogen) 966 defer os.RemoveAll(cryptoPath) 967 968 systemChannelBlockPath := generateBootstrapBlock(t, tempDir, configtxgen, "system", "SampleSoloSystemChannel") 969 systemChannelBlockBytes, err := ioutil.ReadFile(systemChannelBlockPath) 970 assert.NoError(t, err) 971 972 configBlock := &common.Block{} 973 assert.NoError(t, proto.Unmarshal(systemChannelBlockBytes, configBlock)) 974 975 verifier := &cluster_mocks.BlockVerifier{} 976 977 for _, testCase := range []struct { 978 description string 979 ledgerGetOrCreateErr error 980 ledgerHeight uint64 981 lastBlock *common.Block 982 lastConfigBlock *common.Block 983 verifierFromConfigReturns cluster.BlockVerifier 984 verifierFromConfigErr error 985 onFailureInvoked bool 986 expectedPanic string 987 expectedLoggedMessages map[string]struct{} 988 expectedResult verifiersByChannel 989 }{ 990 { 991 description: "obtaining ledger fails", 992 ledgerGetOrCreateErr: errors.New("IO error"), 993 expectedPanic: "Failed obtaining ledger for channel mychannel", 994 }, 995 { 996 description: "empty ledger", 997 expectedLoggedMessages: map[string]struct{}{ 998 "Channel mychannel has no blocks, skipping it": {}, 999 }, 1000 expectedResult: make(verifiersByChannel), 1001 }, 1002 { 1003 description: "block retrieval fails", 1004 ledgerHeight: 100, 1005 expectedPanic: "Failed retrieving block [99] for channel mychannel", 1006 }, 1007 { 1008 description: "block retrieval succeeds but the block is bad", 1009 ledgerHeight: 100, 1010 lastBlock: &common.Block{}, 1011 expectedPanic: "Failed retrieving config block [99] for channel mychannel", 1012 }, 1013 { 1014 description: "config block retrieved is bad", 1015 ledgerHeight: 100, 1016 lastBlock: &common.Block{ 1017 Metadata: &common.BlockMetadata{ 1018 Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{ 1019 Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 21}), 1020 }), {}, {}}, 1021 }, 1022 }, 1023 lastConfigBlock: &common.Block{Header: &common.BlockHeader{Number: 21}}, 1024 expectedPanic: "Failed extracting configuration for channel mychannel from block [21]: empty block", 1025 onFailureInvoked: true, 1026 }, 1027 { 1028 description: "VerifierFromConfig fails", 1029 ledgerHeight: 100, 1030 lastBlock: &common.Block{ 1031 Metadata: &common.BlockMetadata{ 1032 Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{ 1033 Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 21}), 1034 }), {}, {}}, 1035 }, 1036 }, 1037 lastConfigBlock: configBlock, 1038 verifierFromConfigErr: errors.New("failed initializing MSP"), 1039 expectedPanic: "Failed creating verifier for channel mychannel from block [99]: failed initializing MSP", 1040 onFailureInvoked: true, 1041 }, 1042 { 1043 description: "VerifierFromConfig succeeds", 1044 ledgerHeight: 100, 1045 lastBlock: &common.Block{ 1046 Metadata: &common.BlockMetadata{ 1047 Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{ 1048 Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 21}), 1049 }), {}, {}}, 1050 }, 1051 }, 1052 lastConfigBlock: configBlock, 1053 expectedLoggedMessages: map[string]struct{}{ 1054 "Loaded verifier for channel mychannel from config block at index 99": {}, 1055 }, 1056 expectedResult: verifiersByChannel{ 1057 "mychannel": verifier, 1058 }, 1059 }, 1060 } { 1061 t.Run(testCase.description, func(t *testing.T) { 1062 iterator := &deliver_mocks.BlockIterator{} 1063 iterator.NextReturnsOnCall(0, testCase.lastBlock, common.Status_SUCCESS) 1064 iterator.NextReturnsOnCall(1, testCase.lastConfigBlock, common.Status_SUCCESS) 1065 1066 ledger := &onboarding_mocks.ReadWriter{} 1067 ledger.HeightReturns(testCase.ledgerHeight) 1068 ledger.IteratorReturns(iterator, 1) 1069 1070 ledgerFactory := &onboarding_mocks.Factory{} 1071 ledgerFactory.On("GetOrCreate", "mychannel").Return(ledger, testCase.ledgerGetOrCreateErr) 1072 ledgerFactory.On("ChannelIDs").Return([]string{"mychannel"}) 1073 1074 verifierFactory := &cluster_mocks.VerifierFactory{} 1075 verifierFactory.On("VerifierFromConfig", mock.Anything, "mychannel").Return(verifier, testCase.verifierFromConfigErr) 1076 1077 var onFailureInvoked bool 1078 onFailure := func(_ *common.Block) { 1079 onFailureInvoked = true 1080 } 1081 1082 verifierLoader := &verifierLoader{ 1083 onFailure: onFailure, 1084 ledgerFactory: ledgerFactory, 1085 verifierFactory: verifierFactory, 1086 logger: flogging.MustGetLogger("test"), 1087 } 1088 1089 verifierLoader.logger = verifierLoader.logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { 1090 delete(testCase.expectedLoggedMessages, entry.Message) 1091 return nil 1092 })) 1093 1094 if testCase.expectedPanic != "" { 1095 f := func() { 1096 verifierLoader.loadVerifiers() 1097 } 1098 assert.PanicsWithValue(t, testCase.expectedPanic, f) 1099 } else { 1100 res := verifierLoader.loadVerifiers() 1101 assert.Equal(t, testCase.expectedResult, res) 1102 } 1103 1104 assert.Equal(t, testCase.onFailureInvoked, onFailureInvoked) 1105 assert.Empty(t, testCase.expectedLoggedMessages) 1106 }) 1107 } 1108 } 1109 1110 func TestValidateBootstrapBlock(t *testing.T) { 1111 cryptoPath := generateCryptoMaterials(t, cryptogen) 1112 defer os.RemoveAll(cryptoPath) 1113 1114 systemChannelBlockPath := generateBootstrapBlock(t, tempDir, configtxgen, "system", "SampleSoloSystemChannel") 1115 systemChannelBlockBytes, err := ioutil.ReadFile(systemChannelBlockPath) 1116 assert.NoError(t, err) 1117 1118 applicationChannelBlockPath := generateBootstrapBlock(t, tempDir, configtxgen, "mychannel", "SampleOrgChannel") 1119 applicationChannelBlockBytes, err := ioutil.ReadFile(applicationChannelBlockPath) 1120 assert.NoError(t, err) 1121 1122 appBlock := &common.Block{} 1123 assert.NoError(t, proto.Unmarshal(applicationChannelBlockBytes, appBlock)) 1124 1125 systemBlock := &common.Block{} 1126 assert.NoError(t, proto.Unmarshal(systemChannelBlockBytes, systemBlock)) 1127 1128 cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) 1129 assert.NoError(t, err) 1130 1131 for _, testCase := range []struct { 1132 description string 1133 block *common.Block 1134 expectedError string 1135 }{ 1136 { 1137 description: "nil block", 1138 expectedError: "nil block", 1139 }, 1140 { 1141 description: "empty block", 1142 block: &common.Block{}, 1143 expectedError: "empty block data", 1144 }, 1145 { 1146 description: "bad envelope", 1147 block: &common.Block{ 1148 Data: &common.BlockData{ 1149 Data: [][]byte{{1, 2, 3}}, 1150 }, 1151 }, 1152 expectedError: "failed extracting envelope from block: " + 1153 "proto: common.Envelope: illegal tag 0 (wire type 1)", 1154 }, 1155 { 1156 description: "application channel block", 1157 block: appBlock, 1158 expectedError: "the block isn't a system channel block because it lacks ConsortiumsConfig", 1159 }, 1160 { 1161 description: "system channel block", 1162 block: systemBlock, 1163 }, 1164 } { 1165 t.Run(testCase.description, func(t *testing.T) { 1166 err := ValidateBootstrapBlock(testCase.block, cryptoProvider) 1167 if testCase.expectedError == "" { 1168 assert.NoError(t, err) 1169 return 1170 } 1171 1172 assert.EqualError(t, err, testCase.expectedError) 1173 }) 1174 } 1175 } 1176 1177 func generateBootstrapBlock(t *testing.T, tempDir, configtxgen, channel, profile string) string { 1178 gt := NewGomegaWithT(t) 1179 // create a genesis block for the specified channel and profile 1180 genesisBlockPath := filepath.Join(tempDir, channel+".block") 1181 cmd := exec.Command( 1182 configtxgen, 1183 "-channelID", channel, 1184 "-profile", profile, 1185 "-outputBlock", genesisBlockPath, 1186 "--configPath", tempDir, 1187 ) 1188 configtxgenProcess, err := gexec.Start(cmd, nil, nil) 1189 gt.Expect(err).NotTo(HaveOccurred()) 1190 gt.Eventually(configtxgenProcess, time.Minute).Should(gexec.Exit(0)) 1191 gt.Expect(configtxgenProcess.Err).To(gbytes.Say("Writing genesis block")) 1192 1193 return genesisBlockPath 1194 } 1195 1196 // generateCryptoMaterials uses cryptogen to generate the necessary 1197 // MSP files and TLS certificates 1198 func generateCryptoMaterials(t *testing.T, cryptogen string) string { 1199 gt := NewGomegaWithT(t) 1200 cryptoPath := filepath.Join(tempDir, "crypto") 1201 1202 cmd := exec.Command( 1203 cryptogen, 1204 "generate", 1205 "--config", filepath.Join(tempDir, "examplecom-config.yaml"), 1206 "--output", cryptoPath, 1207 ) 1208 cryptogenProcess, err := gexec.Start(cmd, nil, nil) 1209 gt.Expect(err).NotTo(HaveOccurred()) 1210 gt.Eventually(cryptogenProcess, time.Minute).Should(gexec.Exit(0)) 1211 1212 return cryptoPath 1213 } 1214 1215 func TestCreateReplicator(t *testing.T) { 1216 cleanup := configtest.SetDevFabricConfigPath(t) 1217 defer cleanup() 1218 bootBlock := encoder.New(genesisconfig.Load(genesisconfig.SampleDevModeSoloProfile)).GenesisBlockForChannel("system") 1219 1220 iterator := &deliver_mocks.BlockIterator{} 1221 iterator.NextReturnsOnCall(0, bootBlock, common.Status_SUCCESS) 1222 iterator.NextReturnsOnCall(1, bootBlock, common.Status_SUCCESS) 1223 1224 ledger := &onboarding_mocks.ReadWriter{} 1225 ledger.HeightReturns(1) 1226 ledger.IteratorReturns(iterator, 1) 1227 1228 ledgerFactory := &onboarding_mocks.Factory{} 1229 ledgerFactory.On("GetOrCreate", "mychannel").Return(ledger, nil) 1230 ledgerFactory.On("ChannelIDs").Return([]string{"mychannel"}) 1231 1232 cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) 1233 assert.NoError(t, err) 1234 1235 signer := &onboarding_mocks.SignerSerializer{} 1236 r := NewReplicationInitiator(ledgerFactory, bootBlock, &localconfig.TopLevel{}, comm.SecureOptions{}, signer, cryptoProvider) 1237 1238 err = r.verifierRetriever.RetrieveVerifier("mychannel").VerifyBlockSignature(nil, nil) 1239 assert.EqualError(t, err, "implicit policy evaluation failed - 0 sub-policies were satisfied, but this policy requires 1 of the 'Writers' sub-policies to be satisfied") 1240 1241 err = r.verifierRetriever.RetrieveVerifier("system").VerifyBlockSignature(nil, nil) 1242 assert.NoError(t, err) 1243 }