/* Copyright IBM Corp. 2017 All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package cluster_test import ( "bytes" "context" "crypto/x509" "encoding/pem" "errors" "fmt" "io/ioutil" "math" "strings" "sync" "testing" "time" "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/msp" "github.com/hyperledger/fabric-protos-go/orderer" "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/bccsp/sw" "github.com/hyperledger/fabric/common/capabilities" "github.com/hyperledger/fabric/common/channelconfig" "github.com/hyperledger/fabric/common/configtx" "github.com/hyperledger/fabric/common/configtx/test" "github.com/hyperledger/fabric/common/crypto/tlsgen" "github.com/hyperledger/fabric/common/flogging" "github.com/hyperledger/fabric/common/metrics/disabled" "github.com/hyperledger/fabric/common/policies" "github.com/hyperledger/fabric/core/config/configtest" "github.com/hyperledger/fabric/internal/configtxgen/encoder" "github.com/hyperledger/fabric/internal/configtxgen/genesisconfig" "github.com/hyperledger/fabric/internal/pkg/comm" "github.com/hyperledger/fabric/orderer/common/cluster" "github.com/hyperledger/fabric/orderer/common/cluster/mocks" "github.com/hyperledger/fabric/protoutil" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) //go:generate counterfeiter -o mocks/policy.go --fake-name Policy . policy type policy interface { policies.Policy } //go:generate counterfeiter -o mocks/policy_manager.go --fake-name PolicyManager . policyManager type policyManager interface { policies.Manager } func TestParallelStubActivation(t *testing.T) { // Scenario: Activate the stub from different goroutines in parallel. stub := &cluster.Stub{} var wg sync.WaitGroup n := 100 wg.Add(n) instance := &cluster.RemoteContext{} var activationCount int maybeCreateInstance := func() (*cluster.RemoteContext, error) { activationCount++ return instance, nil } for i := 0; i < n; i++ { go func() { defer wg.Done() stub.Activate(maybeCreateInstance) }() } wg.Wait() activatedStub := stub.RemoteContext // Ensure the instance is the reference we stored // and not any other reference, i.e - it wasn't // copied by value. require.True(t, activatedStub == instance) // Ensure the method was invoked only once. require.Equal(t, activationCount, 1) } func TestDialerCustomKeepAliveOptions(t *testing.T) { ca, err := tlsgen.NewCA() require.NoError(t, err) clientKeyPair, err := ca.NewClientCertKeyPair() require.NoError(t, err) clientConfig := comm.ClientConfig{ KaOpts: comm.KeepaliveOptions{ ClientTimeout: time.Second * 12345, }, DialTimeout: time.Millisecond * 100, SecOpts: comm.SecureOptions{ RequireClientCert: true, Key: clientKeyPair.Key, Certificate: clientKeyPair.Cert, ServerRootCAs: [][]byte{ca.CertBytes()}, UseTLS: true, ClientRootCAs: [][]byte{ca.CertBytes()}, }, } dialer := &cluster.PredicateDialer{Config: clientConfig} timeout := dialer.Config.KaOpts.ClientTimeout require.Equal(t, time.Second*12345, timeout) } func TestPredicateDialerUpdateRootCAs(t *testing.T) { node1 := newTestNode(t) defer node1.stop() anotherTLSCA, err := tlsgen.NewCA() require.NoError(t, err) dialer := &cluster.PredicateDialer{ Config: node1.clientConfig, } dialer.Config.SecOpts.ServerRootCAs = [][]byte{anotherTLSCA.CertBytes()} dialer.Config.DialTimeout = time.Second dialer.Config.AsyncConnect = false _, err = dialer.Dial(node1.srv.Address(), nil) require.Error(t, err) // Update root TLS CAs asynchronously to make sure we don't have a data race. go func() { dialer.UpdateRootCAs(node1.clientConfig.SecOpts.ServerRootCAs) }() // Eventually we should succeed connecting. for i := 0; i < 10; i++ { conn, err := dialer.Dial(node1.srv.Address(), nil) if err == nil { conn.Close() return } } require.Fail(t, "could not connect after 10 attempts despite changing TLS CAs") } func TestDialerBadConfig(t *testing.T) { emptyCertificate := []byte("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----") dialer := &cluster.PredicateDialer{ Config: comm.ClientConfig{ SecOpts: comm.SecureOptions{ UseTLS: true, ServerRootCAs: [][]byte{emptyCertificate}, }, }, } _, err := dialer.Dial("127.0.0.1:8080", func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return nil }) require.ErrorContains(t, err, "error adding root certificate") } func TestDERtoPEM(t *testing.T) { ca, err := tlsgen.NewCA() require.NoError(t, err) keyPair, err := ca.NewServerCertKeyPair("localhost") require.NoError(t, err) require.Equal(t, cluster.DERtoPEM(keyPair.TLSCert.Raw), string(keyPair.Cert)) } func TestStandardDialer(t *testing.T) { emptyCertificate := []byte("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----") certPool := [][]byte{emptyCertificate} config := comm.ClientConfig{SecOpts: comm.SecureOptions{UseTLS: true, ServerRootCAs: certPool}} standardDialer := &cluster.StandardDialer{ Config: config, } _, err := standardDialer.Dial(cluster.EndpointCriteria{Endpoint: "127.0.0.1:8080", TLSRootCAs: certPool}) require.ErrorContains(t, err, "error adding root certificate") } func TestVerifyBlockHash(t *testing.T) { var start uint64 = 3 var end uint64 = 23 verify := func(blockchain []*common.Block) error { for i := 0; i < len(blockchain); i++ { err := cluster.VerifyBlockHash(i, blockchain) if err != nil { return err } } return nil } // Verify that createBlockChain() creates a valid blockchain require.NoError(t, verify(createBlockChain(start, end))) twoBlocks := createBlockChain(2, 3) twoBlocks[0].Header = nil require.EqualError(t, cluster.VerifyBlockHash(1, twoBlocks), "previous block header is nil") // Index out of bounds blockchain := createBlockChain(start, end) err := cluster.VerifyBlockHash(100, blockchain) require.EqualError(t, err, "index 100 out of bounds (total 21 blocks)") for _, testCase := range []struct { name string mutateBlockSequence func([]*common.Block) []*common.Block errorContains string }{ { name: "non consecutive sequences", errorContains: "sequences 12 and 666 were received consecutively", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.Number = 666 assignHashes(blockSequence) return blockSequence }, }, { name: "data hash mismatch", errorContains: "computed hash of block (13) (dcb2ec1c5e482e4914cb953ff8eedd12774b244b12912afbe6001ba5de9ff800)" + " doesn't match claimed hash (07)", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.DataHash = []byte{7} return blockSequence }, }, { name: "prev hash mismatch", errorContains: "block [12]'s hash " + "(866351705f1c2f13e10d52ead9d0ca3b80689ede8cc8bf70a6d60c67578323f4) " + "mismatches block [13]'s prev block hash (07)", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[len(blockSequence)/2].Header.PreviousHash = []byte{7} return blockSequence }, }, { name: "nil block header", errorContains: "missing block header", mutateBlockSequence: func(blockSequence []*common.Block) []*common.Block { blockSequence[0].Header = nil return blockSequence }, }, } { testCase := testCase t.Run(testCase.name, func(t *testing.T) { blockchain := createBlockChain(start, end) blockchain = testCase.mutateBlockSequence(blockchain) err := verify(blockchain) require.EqualError(t, err, testCase.errorContains) }) } } func assignHashes(blockchain []*common.Block) { for i := 1; i < len(blockchain); i++ { blockchain[i].Header.PreviousHash = protoutil.BlockHeaderHash(blockchain[i-1].Header) } } func createBlockChain(start, end uint64) []*common.Block { newBlock := func(seq uint64) *common.Block { sHdr := &common.SignatureHeader{ Creator: []byte{1, 2, 3}, Nonce: []byte{9, 5, 42, 66}, } block := protoutil.NewBlock(seq, nil) blockSignature := &common.MetadataSignature{ SignatureHeader: protoutil.MarshalOrPanic(sHdr), } block.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&common.Metadata{ Value: nil, Signatures: []*common.MetadataSignature{ blockSignature, }, }) txn := protoutil.MarshalOrPanic(&common.Envelope{ Payload: protoutil.MarshalOrPanic(&common.Payload{ Header: &common.Header{}, }), }) block.Data.Data = append(block.Data.Data, txn) return block } var blockchain []*common.Block for seq := uint64(start); seq <= uint64(end); seq++ { block := newBlock(seq) block.Data.Data = append(block.Data.Data, make([]byte, 100)) block.Header.DataHash = protoutil.BlockDataHash(block.Data) blockchain = append(blockchain, block) } assignHashes(blockchain) return blockchain } func injectGlobalOrdererEndpoint(t *testing.T, block *common.Block, endpoint string) { ordererAddresses := channelconfig.OrdererAddressesValue([]string{endpoint}) // Unwrap the layers until we reach the orderer addresses env, err := protoutil.ExtractEnvelope(block, 0) require.NoError(t, err) payload, err := protoutil.UnmarshalPayload(env.Payload) require.NoError(t, err) confEnv, err := configtx.UnmarshalConfigEnvelope(payload.Data) require.NoError(t, err) // Replace the orderer addresses confEnv.Config.ChannelGroup.Values[ordererAddresses.Key()] = &common.ConfigValue{ Value: protoutil.MarshalOrPanic(ordererAddresses.Value()), ModPolicy: "/Channel/Orderer/Admins", } // Remove the per org addresses, if applicable ordererGrps := confEnv.Config.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Groups for _, grp := range ordererGrps { if grp.Values[channelconfig.EndpointsKey] == nil { continue } grp.Values[channelconfig.EndpointsKey].Value = nil } // And put it back into the block payload.Data = protoutil.MarshalOrPanic(confEnv) env.Payload = protoutil.MarshalOrPanic(payload) block.Data.Data[0] = protoutil.MarshalOrPanic(env) } func TestEndpointconfigFromConfigBlockGreenPath(t *testing.T) { t.Run("global endpoints", func(t *testing.T) { block, err := test.MakeGenesisBlock("mychannel") require.NoError(t, err) cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) require.NoError(t, err) // For a block that doesn't have per org endpoints, // we take the global endpoints injectGlobalOrdererEndpoint(t, block, "globalEndpoint") endpointConfig, err := cluster.EndpointconfigFromConfigBlock(block, cryptoProvider) require.NoError(t, err) require.Len(t, endpointConfig, 1) require.Equal(t, "globalEndpoint", endpointConfig[0].Endpoint) bl, _ := pem.Decode(endpointConfig[0].TLSRootCAs[0]) cert, err := x509.ParseCertificate(bl.Bytes) require.NoError(t, err) require.True(t, cert.IsCA) }) t.Run("per org endpoints", func(t *testing.T) { block, err := test.MakeGenesisBlock("mychannel") require.NoError(t, err) // Make a second config. gConf := genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile, configtest.GetDevConfigDir()) gConf.Orderer.Capabilities = map[string]bool{ capabilities.OrdererV2_0: true, } channelGroup, err := encoder.NewChannelGroup(gConf) require.NoError(t, err) cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) require.NoError(t, err) bundle, err := channelconfig.NewBundle("mychannel", &common.Config{ChannelGroup: channelGroup}, cryptoProvider) require.NoError(t, err) msps, err := bundle.MSPManager().GetMSPs() require.NoError(t, err) caBytes := msps["SampleOrg"].GetTLSRootCerts()[0] require.NoError(t, err) injectAdditionalTLSCAEndpointPair(t, block, "anotherEndpoint", caBytes, "fooOrg") endpointConfig, err := cluster.EndpointconfigFromConfigBlock(block, cryptoProvider) require.NoError(t, err) // And ensure that the endpoints that are taken, are the per org ones. require.Len(t, endpointConfig, 2) for _, endpoint := range endpointConfig { // If this is the original organization (and not the clone), // the TLS CA is 'caBytes' read from the second block. if endpoint.Endpoint == "anotherEndpoint" { require.Len(t, endpoint.TLSRootCAs, 1) require.Equal(t, caBytes, endpoint.TLSRootCAs[0]) continue } // Else, our endpoints are from the original org, and the TLS CA is something else. require.NotEqual(t, caBytes, endpoint.TLSRootCAs[0]) // The endpoints we expect to see are something else. require.Equal(t, 0, strings.Index(endpoint.Endpoint, "127.0.0.1:")) bl, _ := pem.Decode(endpoint.TLSRootCAs[0]) cert, err := x509.ParseCertificate(bl.Bytes) require.NoError(t, err) require.True(t, cert.IsCA) } }) } func TestEndpointconfigFromConfigBlockFailures(t *testing.T) { cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) require.NoError(t, err) t.Run("nil block", func(t *testing.T) { certs, err := cluster.EndpointconfigFromConfigBlock(nil, cryptoProvider) require.Nil(t, certs) require.EqualError(t, err, "nil block") }) t.Run("nil block data", func(t *testing.T) { certs, err := cluster.EndpointconfigFromConfigBlock(&common.Block{}, cryptoProvider) require.Nil(t, certs) require.EqualError(t, err, "block data is nil") }) t.Run("no envelope", func(t *testing.T) { certs, err := cluster.EndpointconfigFromConfigBlock(&common.Block{ Data: &common.BlockData{}, }, cryptoProvider) require.Nil(t, certs) require.EqualError(t, err, "envelope index out of bounds") }) t.Run("bad envelope", func(t *testing.T) { certs, err := cluster.EndpointconfigFromConfigBlock(&common.Block{ Data: &common.BlockData{ Data: [][]byte{{}}, }, }, cryptoProvider) require.Nil(t, certs) require.EqualError(t, err, "failed extracting bundle from envelope: envelope header cannot be nil") }) } func TestConfigFromBlockBadInput(t *testing.T) { for _, testCase := range []struct { name string block *common.Block expectedError string }{ { name: "nil block", expectedError: "empty block", block: nil, }, { name: "nil block data", expectedError: "empty block", block: &common.Block{}, }, { name: "no data in block", expectedError: "empty block", block: &common.Block{Data: &common.BlockData{}}, }, { name: "invalid payload", expectedError: "error unmarshalling Envelope", block: &common.Block{Data: &common.BlockData{Data: [][]byte{{1, 2, 3}}}}, }, { name: "bad genesis block", expectedError: "invalid config envelope", block: &common.Block{ Header: &common.BlockHeader{}, Data: &common.BlockData{Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ Payload: protoutil.MarshalOrPanic(&common.Payload{ Data: []byte{1, 2, 3}, }), })}}, }, }, { name: "invalid envelope in block", expectedError: "error unmarshalling Envelope", block: &common.Block{Data: &common.BlockData{Data: [][]byte{{1, 2, 3}}}}, }, { name: "invalid payload in block envelope", expectedError: "error unmarshalling Payload", block: &common.Block{Data: &common.BlockData{Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ Payload: []byte{1, 2, 3}, })}}}, }, { name: "invalid channel header", expectedError: "error unmarshalling ChannelHeader", block: &common.Block{ Header: &common.BlockHeader{Number: 1}, Data: &common.BlockData{Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ Payload: protoutil.MarshalOrPanic(&common.Payload{ Header: &common.Header{ ChannelHeader: []byte{1, 2, 3}, }, }), })}}, }, }, { name: "invalid config block", expectedError: "invalid config envelope", block: &common.Block{ Header: &common.BlockHeader{}, Data: &common.BlockData{Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ Payload: protoutil.MarshalOrPanic(&common.Payload{ Data: []byte{1, 2, 3}, Header: &common.Header{ ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ Type: int32(common.HeaderType_CONFIG), }), }, }), })}}, }, }, } { t.Run(testCase.name, func(t *testing.T) { conf, err := cluster.ConfigFromBlock(testCase.block) require.Nil(t, conf) require.Error(t, err) require.Contains(t, err.Error(), testCase.expectedError) }) } } func TestBlockValidationPolicyVerifier(t *testing.T) { config := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir()) group, err := encoder.NewChannelGroup(config) require.NoError(t, err) require.NotNil(t, group) cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) require.NoError(t, err) validConfigEnvelope := &common.ConfigEnvelope{ Config: &common.Config{ ChannelGroup: group, }, } for _, testCase := range []struct { description string expectedError string envelope *common.ConfigEnvelope policyMap map[string]policies.Policy policy policies.Policy }{ /** { description: "policy not found", expectedError: "policy /Channel/Orderer/BlockValidation wasn't found", }, */ { description: "policy evaluation fails", expectedError: "invalid signature", policy: &mocks.Policy{ EvaluateSignedDataStub: func([]*protoutil.SignedData) error { return errors.New("invalid signature") }, }, }, { description: "bad config envelope", expectedError: "config must contain a channel group", policy: &mocks.Policy{}, envelope: &common.ConfigEnvelope{Config: &common.Config{}}, }, { description: "good config envelope overrides custom policy manager", policy: &mocks.Policy{ EvaluateSignedDataStub: func([]*protoutil.SignedData) error { return errors.New("invalid signature") }, }, envelope: validConfigEnvelope, }, } { t.Run(testCase.description, func(t *testing.T) { mockPolicyManager := &mocks.PolicyManager{} if testCase.policy != nil { mockPolicyManager.GetPolicyReturns(testCase.policy, true) } else { mockPolicyManager.GetPolicyReturns(nil, false) } mockPolicyManager.GetPolicyReturns(testCase.policy, true) verifier := &cluster.BlockValidationPolicyVerifier{ Logger: flogging.MustGetLogger("test"), Channel: "mychannel", PolicyMgr: mockPolicyManager, BCCSP: cryptoProvider, } err := verifier.VerifyBlockSignature(nil, testCase.envelope) if testCase.expectedError != "" { require.EqualError(t, err, testCase.expectedError) } else { require.NoError(t, err) } }) } } func TestBlockVerifierAssembler(t *testing.T) { config := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir()) group, err := encoder.NewChannelGroup(config) require.NoError(t, err) require.NotNil(t, group) cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) require.NoError(t, err) t.Run("Good config envelope", func(t *testing.T) { bva := &cluster.BlockVerifierAssembler{BCCSP: cryptoProvider} verifier, err := bva.VerifierFromConfig(&common.ConfigEnvelope{ Config: &common.Config{ ChannelGroup: group, }, }, "mychannel") require.NoError(t, err) require.Error(t, verifier(nil, nil)) }) t.Run("Bad config envelope", func(t *testing.T) { bva := &cluster.BlockVerifierAssembler{BCCSP: cryptoProvider} _, err := bva.VerifierFromConfig(&common.ConfigEnvelope{}, "mychannel") require.EqualError(t, err, "channelconfig Config cannot be nil") }) } func TestLastConfigBlock(t *testing.T) { blockRetriever := &mocks.BlockRetriever{} blockRetriever.On("Block", uint64(42)).Return(&common.Block{}) blockRetriever.On("Block", uint64(666)).Return(nil) for _, testCase := range []struct { name string block *common.Block blockRetriever cluster.BlockRetriever expectedError string }{ { name: "nil block", expectedError: "nil block", blockRetriever: blockRetriever, }, { name: "nil support", expectedError: "nil blockRetriever", block: &common.Block{}, }, { name: "nil metadata", expectedError: "failed to retrieve metadata: no metadata in block", blockRetriever: blockRetriever, block: &common.Block{}, }, { name: "no block with index", block: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{ Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 666}), })}, }, }, expectedError: "unable to retrieve last config block [666]", blockRetriever: blockRetriever, }, { name: "valid last config block", block: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: [][]byte{{}, protoutil.MarshalOrPanic(&common.Metadata{ Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 42}), })}, }, }, blockRetriever: blockRetriever, }, } { testCase := testCase t.Run(testCase.name, func(t *testing.T) { block, err := cluster.LastConfigBlock(testCase.block, testCase.blockRetriever) if testCase.expectedError == "" { require.NoError(t, err) require.NotNil(t, block) return } require.EqualError(t, err, testCase.expectedError) require.Nil(t, block) }) } } func TestVerificationRegistryRegisterVerifier(t *testing.T) { blockBytes, err := ioutil.ReadFile("testdata/mychannel.block") require.NoError(t, err) block := &common.Block{} require.NoError(t, proto.Unmarshal(blockBytes, block)) mockErr := errors.New("Mock error") verifier := func(header *common.BlockHeader, metadata *common.BlockMetadata) error { return mockErr } verifierFactory := &mocks.VerifierFactory{} verifierFactory.On("VerifierFromConfig", mock.Anything, "mychannel").Return(verifier, nil) registry := &cluster.VerificationRegistry{ Logger: flogging.MustGetLogger("test"), VerifiersByChannel: make(map[string]protoutil.BlockVerifierFunc), VerifierFactory: verifierFactory, } var loadCount int registry.LoadVerifier = func(chain string) protoutil.BlockVerifierFunc { require.Equal(t, "mychannel", chain) loadCount++ return verifier } v := registry.RetrieveVerifier("mychannel") require.Nil(t, v) registry.RegisterVerifier("mychannel") v = registry.RetrieveVerifier("mychannel") require.Same(t, verifier(nil, nil), v(nil, nil)) require.Equal(t, 1, loadCount) // If the verifier exists, this is a no-op registry.RegisterVerifier("mychannel") require.Equal(t, 1, loadCount) } func TestVerificationRegistry(t *testing.T) { blockBytes, err := ioutil.ReadFile("testdata/mychannel.block") require.NoError(t, err) block := &common.Block{} require.NoError(t, proto.Unmarshal(blockBytes, block)) flogging.ActivateSpec("test=DEBUG") defer flogging.Reset() mockErr := errors.New("Mock error") verifier := func(header *common.BlockHeader, metadata *common.BlockMetadata) error { return mockErr } for _, testCase := range []struct { description string verifiersByChannel map[string]protoutil.BlockVerifierFunc blockCommitted *common.Block channelCommitted string channelRetrieved string expectedVerifier protoutil.BlockVerifierFunc verifierFromConfig protoutil.BlockVerifierFunc verifierFromConfigErr error loggedMessages map[string]struct{} }{ { description: "bad block", blockCommitted: &common.Block{}, channelRetrieved: "foo", channelCommitted: "foo", loggedMessages: map[string]struct{}{ "Failed parsing block of channel foo: empty block, content: " + "{\n\t\"data\": null,\n\t\"header\": null,\n\t\"metadata\": null\n}\n": {}, "No verifier for channel foo exists": {}, }, expectedVerifier: nil, }, { description: "not a config block", blockCommitted: createBlockChain(5, 5)[0], channelRetrieved: "foo", channelCommitted: "foo", loggedMessages: map[string]struct{}{ "No verifier for channel foo exists": {}, "Committed block [5] for channel foo that is not a config block": {}, }, expectedVerifier: nil, }, { description: "valid block but verifier from config fails", blockCommitted: block, verifierFromConfigErr: errors.New("invalid MSP config"), channelRetrieved: "bar", channelCommitted: "bar", loggedMessages: map[string]struct{}{ "Failed creating a verifier from a " + "config block for channel bar: invalid MSP config, " + "content: " + cluster.BlockToString(block): {}, "No verifier for channel bar exists": {}, }, expectedVerifier: nil, }, { description: "valid block and verifier from config succeeds but wrong channel retrieved", blockCommitted: block, verifierFromConfig: verifier, channelRetrieved: "foo", channelCommitted: "bar", loggedMessages: map[string]struct{}{ "No verifier for channel foo exists": {}, "Committed config block [0] for channel bar": {}, }, expectedVerifier: nil, verifiersByChannel: make(map[string]protoutil.BlockVerifierFunc), }, { description: "valid block and verifier from config succeeds", blockCommitted: block, verifierFromConfig: verifier, channelRetrieved: "bar", channelCommitted: "bar", loggedMessages: map[string]struct{}{ "Committed config block [0] for channel bar": {}, }, expectedVerifier: verifier, verifiersByChannel: make(map[string]protoutil.BlockVerifierFunc), }, } { t.Run(testCase.description, func(t *testing.T) { verifierFactory := &mocks.VerifierFactory{} verifierFactory.On("VerifierFromConfig", mock.Anything, testCase.channelCommitted).Return(testCase.verifierFromConfig, testCase.verifierFromConfigErr) registry := &cluster.VerificationRegistry{ Logger: flogging.MustGetLogger("test"), VerifiersByChannel: testCase.verifiersByChannel, VerifierFactory: verifierFactory, } loggedEntriesByMethods := make(map[string]struct{}) // Configure the logger to collect the message logged registry.Logger = registry.Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { loggedEntriesByMethods[entry.Message] = struct{}{} return nil })) registry.BlockCommitted(testCase.blockCommitted, testCase.channelCommitted) verifier := registry.RetrieveVerifier(testCase.channelRetrieved) require.Equal(t, testCase.loggedMessages, loggedEntriesByMethods) if testCase.expectedVerifier == nil { require.Nil(t, verifier) } else { require.NotNil(t, verifier) require.Same(t, testCase.expectedVerifier(nil, nil), verifier(nil, nil)) } }) } } func injectAdditionalTLSCAEndpointPair(t *testing.T, block *common.Block, endpoint string, tlsCA []byte, orgName string) { // Unwrap the layers until we reach the orderer addresses env, err := protoutil.ExtractEnvelope(block, 0) require.NoError(t, err) payload, err := protoutil.UnmarshalPayload(env.Payload) require.NoError(t, err) confEnv, err := configtx.UnmarshalConfigEnvelope(payload.Data) require.NoError(t, err) ordererGrp := confEnv.Config.ChannelGroup.Groups[channelconfig.OrdererGroupKey].Groups // Get the first orderer org config var firstOrdererConfig *common.ConfigGroup for _, grp := range ordererGrp { firstOrdererConfig = grp break } // Duplicate it. secondOrdererConfig := proto.Clone(firstOrdererConfig).(*common.ConfigGroup) ordererGrp[orgName] = secondOrdererConfig // Reach the FabricMSPConfig buried in it. mspConfig := &msp.MSPConfig{} err = proto.Unmarshal(secondOrdererConfig.Values[channelconfig.MSPKey].Value, mspConfig) require.NoError(t, err) fabricConfig := &msp.FabricMSPConfig{} err = proto.Unmarshal(mspConfig.Config, fabricConfig) require.NoError(t, err) // Plant the given TLS CA in it. fabricConfig.TlsRootCerts = [][]byte{tlsCA} // No intermediate root CAs, to make the test simpler. fabricConfig.TlsIntermediateCerts = nil // Rename it. fabricConfig.Name = orgName // Pack the MSP config back into the config secondOrdererConfig.Values[channelconfig.MSPKey].Value = protoutil.MarshalOrPanic(&msp.MSPConfig{ Config: protoutil.MarshalOrPanic(fabricConfig), Type: mspConfig.Type, }) // Inject the endpoint ordererOrgProtos := &common.OrdererAddresses{ Addresses: []string{endpoint}, } secondOrdererConfig.Values[channelconfig.EndpointsKey].Value = protoutil.MarshalOrPanic(ordererOrgProtos) // Fold everything back into the block payload.Data = protoutil.MarshalOrPanic(confEnv) env.Payload = protoutil.MarshalOrPanic(payload) block.Data.Data[0] = protoutil.MarshalOrPanic(env) } func TestEndpointCriteriaString(t *testing.T) { // The top cert is the issuer of the bottom cert certs := `-----BEGIN CERTIFICATE----- MIIBozCCAUigAwIBAgIQMXmzUnikiAZDr4VsrBL+rzAKBggqhkjOPQQDAjAxMS8w LQYDVQQFEyY2NTc2NDA3Njc5ODcwOTA3OTEwNDM5NzkxMTAwNzA0Mzk3Njg3OTAe Fw0xOTExMTEyMDM5MDRaFw0yOTExMDkyMDM5MDRaMDExLzAtBgNVBAUTJjY1NzY0 MDc2Nzk4NzA5MDc5MTA0Mzk3OTExMDA3MDQzOTc2ODc5MFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAEzBBkRvWgasCKf1pejwpOu+1Fv9FffOZMHnna/7lfMrAqOs8d HMDVU7mSexu7YNTpAwm4vkdHXi35H8zlVABTxaNCMEAwDgYDVR0PAQH/BAQDAgGm MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/ MAoGCCqGSM49BAMCA0kAMEYCIQCXqXoYLAJN9diIdGxPlRQJgJLju4brWXZfyt3s E9TjFwIhAOuUJjcOchdP6UA9WLnVWciEo1Omf59NgfHL1gUPb/t6 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBpDCCAUqgAwIBAgIRAIyvtL0z1xQ+NecXeH1HmmAwCgYIKoZIzj0EAwIwMTEv MC0GA1UEBRMmNjU3NjQwNzY3OTg3MDkwNzkxMDQzOTc5MTEwMDcwNDM5NzY4Nzkw HhcNMTkxMTExMjAzOTA0WhcNMTkxMTEyMjAzOTA0WjAyMTAwLgYDVQQFEycxODcw MDQyMzcxODQwMjY5Mzk2ODUxNzk1NzM3MzIyMTc2OTA3MjAwWTATBgcqhkjOPQIB BggqhkjOPQMBBwNCAARZBFDBOfC7T9RbsX+PgyE6sM7ocuwn6krIGjc00ICivFgQ qdHMU7hiswiYwSvwh9MDHlprCRW3ycSgEYQgKU5to0IwQDAOBgNVHQ8BAf8EBAMC BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA8GA1UdEQQIMAaHBH8A AAEwCgYIKoZIzj0EAwIDSAAwRQIhAK6G7qr/ClszCFP25gsflA31+7eoss5vi3o4 qz8bY+s6AiBvO0aOfE8M4ibjmRE4vSXo0+gkOIJKqZcmiRdnJSr8Xw== -----END CERTIFICATE-----` epc := cluster.EndpointCriteria{ Endpoint: "orderer.example.com:7050", TLSRootCAs: [][]byte{[]byte(certs)}, } actual := fmt.Sprint(epc) expected := `{"CAs":[{"Expired":false,"Issuer":"self","Subject":"SERIALNUMBER=65764076798709079104397911007043976879"},{"Expired":true,"Issuer":"SERIALNUMBER=65764076798709079104397911007043976879","Subject":"SERIALNUMBER=187004237184026939685179573732217690720"}],"Endpoint":"orderer.example.com:7050"}` require.Equal(t, expected, actual) } func TestComparisonMemoizer(t *testing.T) { var invocations int m := &cluster.ComparisonMemoizer{ MaxEntries: 5, F: func(a, b []byte) bool { invocations++ return bytes.Equal(a, b) }, } // Warm-up cache for i := 0; i < 5; i++ { notSame := m.Compare([]byte{byte(i)}, []byte{1, 2, 3}) require.False(t, notSame) require.Equal(t, i+1, invocations) } // Ensure lookups are cached for i := 0; i < 5; i++ { notSame := m.Compare([]byte{byte(i)}, []byte{1, 2, 3}) require.False(t, notSame) require.Equal(t, 5, invocations) } // Put a new entry which will cause a cache miss same := m.Compare([]byte{5}, []byte{5}) require.True(t, same) require.Equal(t, 6, invocations) // Keep adding more and more elements to the cache and ensure it stays smaller than its size for i := 0; i < 20; i++ { odd := m.Compare([]byte{byte(1)}, []byte{byte(i % 2)}) require.Equal(t, i%2 != 0, odd) require.LessOrEqual(t, m.Size(), int(m.MaxEntries)) } } //go:generate counterfeiter -o mocks/bccsp.go --fake-name BCCSP . iBCCSP type iBCCSP interface { bccsp.BCCSP } func TestBlockVerifierBuilderEmptyBlock(t *testing.T) { bvfunc := cluster.BlockVerifierBuilder(&mocks.BCCSP{}) block := &common.Block{} verifier := bvfunc(block) require.ErrorContains(t, verifier(nil, nil), "initialized with an invalid config block: block contains no data") } func TestBlockVerifierBuilderNoConfigBlock(t *testing.T) { bvfunc := cluster.BlockVerifierBuilder(&mocks.BCCSP{}) block := createBlockChain(3, 3)[0] verifier := bvfunc(block) md := &common.BlockMetadata{} require.ErrorContains(t, verifier(nil, md), "initialized with an invalid config block: channelconfig Config cannot be nil") } func TestBlockVerifierFunc(t *testing.T) { block := sampleConfigBlock() bvfunc := cluster.BlockVerifierBuilder(&mocks.BCCSP{}) verifier := bvfunc(block) header := &common.BlockHeader{} md := &common.BlockMetadata{ Metadata: [][]byte{ protoutil.MarshalOrPanic(&common.Metadata{Signatures: []*common.MetadataSignature{ { Signature: []byte{}, IdentifierHeader: protoutil.MarshalOrPanic(&common.IdentifierHeader{Identifier: 1}), }, }}), }, } err := verifier(header, md) require.NoError(t, err) } func sampleConfigBlock() *common.Block { return &common.Block{ Header: &common.BlockHeader{ PreviousHash: []byte("foo"), }, Data: &common.BlockData{ Data: [][]byte{ protoutil.MarshalOrPanic(&common.Envelope{ Payload: protoutil.MarshalOrPanic(&common.Payload{ Header: &common.Header{ ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{ Type: int32(common.HeaderType_CONFIG), ChannelId: "mychannel", }), }, Data: protoutil.MarshalOrPanic(&common.ConfigEnvelope{ Config: &common.Config{ ChannelGroup: &common.ConfigGroup{ Values: map[string]*common.ConfigValue{ "Capabilities": { Value: protoutil.MarshalOrPanic(&common.Capabilities{ Capabilities: map[string]*common.Capability{"V3_0": {}}, }), }, "HashingAlgorithm": { Value: protoutil.MarshalOrPanic(&common.HashingAlgorithm{Name: "SHA256"}), }, "BlockDataHashingStructure": { Value: protoutil.MarshalOrPanic(&common.BlockDataHashingStructure{Width: math.MaxUint32}), }, }, Groups: map[string]*common.ConfigGroup{ "Orderer": { Policies: map[string]*common.ConfigPolicy{ "BlockValidation": { Policy: &common.Policy{ Type: 3, }, }, }, Values: map[string]*common.ConfigValue{ "BatchSize": { Value: protoutil.MarshalOrPanic(&orderer.BatchSize{ MaxMessageCount: 500, AbsoluteMaxBytes: 10485760, PreferredMaxBytes: 2097152, }), }, "BatchTimeout": { Value: protoutil.MarshalOrPanic(&orderer.BatchTimeout{ Timeout: "2s", }), }, "Capabilities": { Value: protoutil.MarshalOrPanic(&common.Capabilities{ Capabilities: map[string]*common.Capability{"V3_0": {}}, }), }, "ConsensusType": { Value: protoutil.MarshalOrPanic(&common.BlockData{Data: [][]byte{[]byte("BFT")}}), }, "Orderers": { Value: protoutil.MarshalOrPanic(&common.Orderers{ ConsenterMapping: []*common.Consenter{ { Id: 1, Host: "host1", Port: 8001, MspId: "msp1", Identity: []byte("identity1"), }, }, }), }, }, }, }, }, }, }), }), Signature: []byte("bar"), }), }, }, } } func TestGetTLSSessionBinding(t *testing.T) { serverCert, err := ca.NewServerCertKeyPair("127.0.0.1") require.NoError(t, err) srv, err := comm.NewGRPCServer("127.0.0.1:0", comm.ServerConfig{ SecOpts: comm.SecureOptions{ Certificate: serverCert.Cert, Key: serverCert.Key, UseTLS: true, }, }) require.NoError(t, err) handler := &mocks.Handler{} svc := &cluster.ClusterService{ MinimumExpirationWarningInterval: time.Second * 2, StreamCountReporter: &cluster.StreamCountReporter{ Metrics: cluster.NewMetrics(&disabled.Provider{}), }, Logger: flogging.MustGetLogger("test"), StepLogger: flogging.MustGetLogger("test"), RequestHandler: handler, MembershipByChannel: make(map[string]*cluster.ChannelMembersConfig), } orderer.RegisterClusterNodeServiceServer(srv.Server(), svc) go srv.Start() defer srv.Stop() clientConf := comm.ClientConfig{ DialTimeout: time.Second * 3, SecOpts: comm.SecureOptions{ ServerRootCAs: [][]byte{ca.CertBytes()}, UseTLS: true, }, } conn, err := clientConf.Dial(srv.Address()) require.NoError(t, err) cl := orderer.NewClusterNodeServiceClient(conn) stream, err := cl.Step(context.Background()) require.NoError(t, err) binding, err := cluster.GetTLSSessionBinding(stream.Context(), []byte{1, 2, 3, 4, 5}) require.NoError(t, err) require.Len(t, binding, 32) } func TestChainParticipant(t *testing.T) { for _, testCase := range []struct { name string heightsByEndpoints map[string]uint64 heightsByEndpointsErr error latestBlockSeq uint64 latestBlock *common.Block latestConfigBlockSeq uint64 latestConfigBlock *common.Block expectedError string participantError error }{ { name: "No available orderer", expectedError: cluster.ErrRetryCountExhausted.Error(), }, { name: "Unauthorized for the channel", expectedError: cluster.ErrForbidden.Error(), heightsByEndpointsErr: cluster.ErrForbidden, }, { name: "No OSN services the channel", expectedError: cluster.ErrServiceUnavailable.Error(), heightsByEndpointsErr: cluster.ErrServiceUnavailable, }, { name: "Pulled block has no metadata", heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: &common.Block{}, expectedError: "failed to retrieve metadata: no metadata in block", }, { name: "Pulled block has no last config sequence in metadata", heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: nil, }, }, expectedError: "failed to retrieve metadata: no metadata at index [SIGNATURES]", }, { name: "Pulled block's SIGNATURES metadata is malformed", heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: [][]byte{{1, 2, 3}}, }, }, expectedError: "failed to retrieve metadata: error unmarshalling metadata at index [SIGNATURES]", }, { name: "Pulled block's LAST_CONFIG metadata is malformed", heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: [][]byte{{}, {1, 2, 3}}, }, }, expectedError: "failed to retrieve metadata: error unmarshalling metadata at index [LAST_CONFIG]", }, { name: "Pulled block's metadata is valid and has a last config", heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: [][]byte{protoutil.MarshalOrPanic(&common.Metadata{ Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{ LastConfig: &common.LastConfig{Index: 42}, }), })}, }, }, latestConfigBlockSeq: 42, latestConfigBlock: &common.Block{Header: &common.BlockHeader{Number: 42}}, participantError: cluster.ErrNotInChannel, }, { name: "Failed pulling last block", expectedError: cluster.ErrRetryCountExhausted.Error(), heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: nil, }, { name: "Failed pulling last config block", expectedError: cluster.ErrRetryCountExhausted.Error(), heightsByEndpoints: map[string]uint64{ "orderer.example.com:7050": 100, }, latestBlockSeq: uint64(99), latestBlock: &common.Block{ Metadata: &common.BlockMetadata{ Metadata: [][]byte{protoutil.MarshalOrPanic(&common.Metadata{ Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{ LastConfig: &common.LastConfig{Index: 42}, }), })}, }, }, latestConfigBlockSeq: 42, latestConfigBlock: nil, }, } { t.Run(testCase.name, func(t *testing.T) { puller := &mocks.ChainPuller{} puller.On("HeightsByEndpoints").Return(testCase.heightsByEndpoints, testCase.heightsByEndpointsErr) puller.On("PullBlock", testCase.latestBlockSeq).Return(testCase.latestBlock) puller.On("PullBlock", testCase.latestConfigBlockSeq).Return(testCase.latestConfigBlock) puller.On("Close") // Checks whether the caller participates in the chain. // The ChainPuller should already be calibrated for the chain, // participantError is used to detect whether the caller should service the chain. // err is nil if the caller participates in the chain. // err may be: // ErrNotInChannel in case the caller doesn't participate in the chain. // ErrForbidden in case the caller is forbidden from pulling the block. // ErrServiceUnavailable in case all orderers reachable cannot complete the request. // ErrRetryCountExhausted in case no orderer is reachable. lastConfigBlock, err := cluster.PullLastConfigBlock(puller) configBlocks := make(chan *common.Block, 1) if err == nil { configBlocks <- lastConfigBlock err = testCase.participantError } if testCase.expectedError != "" { require.Error(t, err) require.Contains(t, err.Error(), testCase.expectedError) require.Len(t, configBlocks, 0) } else { require.Len(t, configBlocks, 1) require.Equal(t, testCase.participantError, err) } }) } }