From dd224783b581d2b0224b0d232d75f2fb02434aa2 Mon Sep 17 00:00:00 2001 From: manish Date: Tue, 27 Oct 2020 22:25:15 -0400 Subject: [PATCH] Add function GetChannelConfig in cscc and use in peer client CSCC has a function GetConfigBlock that is used by the peer client to extract the orderer endpoints. A peer bootstrapped from a snapshot may not have the config block until next channel config update is committed. This commit introduces a function a cscc to return channel config and makes use of this in the peer client. Though, in general we do not encourage to add functions in these built-in chanincodes, but here we cannot modify the existing function GetConfigBlock as some external clients may be using this and it will break the backward compatibility. However, the intention is to encourage the users to use this new API to pave the way for the removal of the existing API. Signed-off-by: manish --- core/aclmgmt/defaultaclprovider.go | 1 + core/aclmgmt/resources/resources.go | 1 + core/scc/cscc/configure.go | 26 +++++++ core/scc/cscc/configure_test.go | 94 +++++++++++++++++++++++++ integration/e2e/acl_test.go | 1 + internal/peer/chaincode/common_test.go | 47 ------------- internal/peer/common/common.go | 27 ++++---- internal/peer/common/common_test.go | 96 ++++++++++++++++++++++++++ sampleconfig/configtx.yaml | 3 + 9 files changed, 234 insertions(+), 62 deletions(-) diff --git a/core/aclmgmt/defaultaclprovider.go b/core/aclmgmt/defaultaclprovider.go index cca7355e2fa..ca01d8fbdee 100644 --- a/core/aclmgmt/defaultaclprovider.go +++ b/core/aclmgmt/defaultaclprovider.go @@ -98,6 +98,7 @@ func newDefaultACLProvider(policyChecker policy.PolicyChecker) defaultACLProvide //c resources d.cResourcePolicyMap[resources.Cscc_GetConfigBlock] = CHANNELREADERS + d.cResourcePolicyMap[resources.Cscc_GetChannelConfig] = CHANNELREADERS //---------------- non-scc resources ------------ //Peer resources diff --git a/core/aclmgmt/resources/resources.go b/core/aclmgmt/resources/resources.go index d0c22494877..28c540d87ae 100644 --- a/core/aclmgmt/resources/resources.go +++ b/core/aclmgmt/resources/resources.go @@ -51,6 +51,7 @@ const ( Cscc_JoinChainBySnapshot = "cscc/JoinChainBySnapshot" Cscc_JoinBySnapshotStatus = "cscc/JoinBySnapshotStatus" Cscc_GetConfigBlock = "cscc/GetConfigBlock" + Cscc_GetChannelConfig = "cscc/GetChannelConfig" Cscc_GetChannels = "cscc/GetChannels" //Peer resources diff --git a/core/scc/cscc/configure.go b/core/scc/cscc/configure.go index 97abd452703..1312b932a8d 100644 --- a/core/scc/cscc/configure.go +++ b/core/scc/cscc/configure.go @@ -81,6 +81,7 @@ const ( JoinChainBySnapshot string = "JoinChainBySnapshot" JoinBySnapshotStatus string = "JoinBySnapshotStatus" GetConfigBlock string = "GetConfigBlock" + GetChannelConfig string = "GetChannelConfig" GetChannels string = "GetChannels" ) @@ -198,6 +199,14 @@ func (e *PeerConfiger) InvokeNoShim(args [][]byte, sp *pb.SignedProposal) pb.Res } return e.getConfigBlock(args[1]) + case GetChannelConfig: + if len(args[1]) == 0 { + return shim.Error("empty channel name provided") + } + if err = e.aclProvider.CheckACL(resources.Cscc_GetChannelConfig, string(args[1]), sp); err != nil { + return shim.Error(fmt.Sprintf("access denied for [%s][%s]: %s", fname, args[1], err)) + } + return e.getChannelConfig(args[1]) case GetChannels: // 2. check get channels policy if err = e.aclProvider.CheckACL(resources.Cscc_GetChannels, "", sp); err != nil { @@ -304,6 +313,23 @@ func (e *PeerConfiger) getConfigBlock(channelID []byte) pb.Response { return shim.Success(blockBytes) } +func (e *PeerConfiger) getChannelConfig(channelID []byte) pb.Response { + channel := e.peer.Channel(string(channelID)) + if channel == nil { + return shim.Error(fmt.Sprintf("unknown channel ID, %s", string(channelID))) + } + channelConfig, err := peer.RetrievePersistedChannelConfig(channel.Ledger()) + if err != nil { + return shim.Error(err.Error()) + } + + channelConfigBytes, err := protoutil.Marshal(channelConfig) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(channelConfigBytes) +} + // getChannels returns information about all channels for this peer func (e *PeerConfiger) getChannels() pb.Response { channelInfoArray := e.peer.GetChannelsInfo() diff --git a/core/scc/cscc/configure_test.go b/core/scc/cscc/configure_test.go index 4f9368e94d4..a085ffd4434 100644 --- a/core/scc/cscc/configure_test.go +++ b/core/scc/cscc/configure_test.go @@ -450,6 +450,91 @@ func TestConfigerInvokeJoinChainBySnapshot(t *testing.T) { require.Contains(t, res.Message, "access denied for [JoinChainBySnapshot]") } +func TestConfigerInvokeGetChannelConfig(t *testing.T) { + testDir, err := ioutil.TempDir("", "cscc_test_GetChannelConfig") + require.NoError(t, err) + defer os.RemoveAll(testDir) + + ledgerInitializer := ledgermgmttest.NewInitializer(testDir) + ledgerInitializer.CustomTxProcessors = map[common.HeaderType]ledger.CustomTxProcessor{ + common.HeaderType_CONFIG: &peer.ConfigTxProcessor{}, + } + ledgerMgr := ledgermgmt.NewLedgerMgr(ledgerInitializer) + defer ledgerMgr.Close() + + listener, err := net.Listen("tcp", "127.0.0.1:") + require.NoError(t, err) + grpcServer := grpc.NewServer() + + cscc := newPeerConfiger(t, ledgerMgr, grpcServer, listener.Addr().String()) + + go grpcServer.Serve(listener) + defer grpcServer.Stop() + + block, err := configtxtest.MakeGenesisBlock("test-channel-id") + require.NoError(t, err) + require.NoError(t, + cscc.peer.CreateChannel("test-channel-id", block, cscc.deployedCCInfoProvider, cscc.legacyLifecycle, cscc.newLifecycle), + ) + + initMocks := func() (*mocks.ACLProvider, *mocks.ChaincodeStub) { + mockACLProvider := cscc.aclProvider.(*mocks.ACLProvider) + mockACLProvider.CheckACLReturns(nil) + + mockStub := &mocks.ChaincodeStub{} + mockStub.GetSignedProposalReturns(validSignedProposal(), nil) + mockStub.GetArgsReturns([][]byte{[]byte("GetChannelConfig"), []byte("test-channel-id")}) + return mockACLProvider, mockStub + } + + t.Run("green-path", func(t *testing.T) { + _, mockStub := initMocks() + res := cscc.Invoke(mockStub) + require.Equal(t, int32(shim.OK), res.Status) + + retrievedChannelConfig := &cb.Config{} + require.NoError(t, proto.Unmarshal(res.Payload, retrievedChannelConfig)) + require.True(t, + proto.Equal( + channelConfigFromBlock(t, block), + retrievedChannelConfig, + ), + ) + }) + + t.Run("acl-error", func(t *testing.T) { + mockACLProvider, mockStub := initMocks() + mockACLProvider.CheckACLReturns(errors.New("auth error")) + res := cscc.Invoke(mockStub) + require.Equal(t, int32(shim.ERROR), res.Status) + require.Equal(t, res.Message, "access denied for [GetChannelConfig][test-channel-id]: auth error") + }) + + t.Run("missing-channel-name-error", func(t *testing.T) { + _, mockStub := initMocks() + mockStub.GetArgsReturns([][]byte{[]byte("GetChannelConfig")}) + res := cscc.Invoke(mockStub) + require.Equal(t, int32(shim.ERROR), res.Status) + require.Equal(t, "Incorrect number of arguments, 1", res.Message) + }) + + t.Run("nil-channel-name-error", func(t *testing.T) { + _, mockStub := initMocks() + mockStub.GetArgsReturns([][]byte{[]byte("GetChannelConfig"), {}}) + res := cscc.Invoke(mockStub) + require.Equal(t, int32(shim.ERROR), res.Status) + require.Equal(t, "empty channel name provided", res.Message) + }) + + t.Run("non-existing-channel-name-error", func(t *testing.T) { + _, mockStub := initMocks() + mockStub.GetArgsReturns([][]byte{[]byte("GetChannelConfig"), []byte("non-existing-channel")}) + res := cscc.Invoke(mockStub) + require.Equal(t, int32(shim.ERROR), res.Status) + require.Equal(t, "unknown channel ID, non-existing-channel", res.Message) + }) +} + func TestPeerConfiger_SubmittingOrdererGenesis(t *testing.T) { conf := genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile, configtest.GetDevConfigDir()) conf.Application = nil @@ -570,3 +655,12 @@ func validSignedProposal() *pb.SignedProposal { }), } } + +func channelConfigFromBlock(t *testing.T, configBlock *cb.Block) *cb.Config { + envelopeConfig, err := protoutil.ExtractEnvelope(configBlock, 0) + require.NoError(t, err) + configEnv := &common.ConfigEnvelope{} + _, err = protoutil.UnmarshalEnvelopeOfType(envelopeConfig, common.HeaderType_CONFIG, configEnv) + require.NoError(t, err) + return configEnv.Config +} diff --git a/integration/e2e/acl_test.go b/integration/e2e/acl_test.go index d4045ca1016..507c845abcd 100644 --- a/integration/e2e/acl_test.go +++ b/integration/e2e/acl_test.go @@ -234,6 +234,7 @@ var _ = Describe("EndToEndACL", func() { // cscc // ItEnforcesPolicy("cscc", "GetConfigBlock", "testchannel") + ItEnforcesPolicy("cscc", "GetChannelConfig", "testchannel") // // _lifecycle ACL policies diff --git a/internal/peer/chaincode/common_test.go b/internal/peer/chaincode/common_test.go index 5de2dd9d43c..dbda4455bc7 100644 --- a/internal/peer/chaincode/common_test.go +++ b/internal/peer/chaincode/common_test.go @@ -20,16 +20,12 @@ import ( "github.com/golang/protobuf/proto" cb "github.com/hyperledger/fabric-protos-go/common" pb "github.com/hyperledger/fabric-protos-go/peer" - "github.com/hyperledger/fabric/bccsp/factory" "github.com/hyperledger/fabric/bccsp/sw" "github.com/hyperledger/fabric/common/policydsl" "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/peer/chaincode/mock" "github.com/hyperledger/fabric/internal/peer/common" "github.com/hyperledger/fabric/internal/pkg/identity" - "github.com/hyperledger/fabric/protoutil" . "github.com/onsi/gomega" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -153,49 +149,6 @@ func TestCheckInvalidJSON(t *testing.T) { } } -func TestGetOrdererEndpointFromConfigTx(t *testing.T) { - signer, err := common.GetDefaultSigner() - require.NoError(t, err) - - mockchain := "mockchain" - factory.InitFactories(nil) - config := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir()) - pgen := encoder.New(config) - genesisBlock := pgen.GenesisBlockForChannel(mockchain) - - mockResponse := &pb.ProposalResponse{ - Response: &pb.Response{Status: 200, Payload: protoutil.MarshalOrPanic(genesisBlock)}, - Endorsement: &pb.Endorsement{}, - } - mockEndorserClient := common.GetMockEndorserClient(mockResponse, nil) - - cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) - require.NoError(t, err) - ordererEndpoints, err := common.GetOrdererEndpointOfChain(mockchain, signer, mockEndorserClient, cryptoProvider) - require.NoError(t, err, "GetOrdererEndpointOfChain from genesis block") - - require.Equal(t, len(ordererEndpoints), 0) -} - -func TestGetOrdererEndpointFail(t *testing.T) { - signer, err := common.GetDefaultSigner() - require.NoError(t, err) - - mockchain := "mockchain" - factory.InitFactories(nil) - - mockResponse := &pb.ProposalResponse{ - Response: &pb.Response{Status: 404, Payload: []byte{}}, - Endorsement: &pb.Endorsement{}, - } - mockEndorserClient := common.GetMockEndorserClient(mockResponse, nil) - - cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) - require.NoError(t, err) - _, err = common.GetOrdererEndpointOfChain(mockchain, signer, mockEndorserClient, cryptoProvider) - require.Error(t, err, "GetOrdererEndpointOfChain from invalid response") -} - const sampleCollectionConfigGood = `[ { "name": "foo", diff --git a/internal/peer/common/common.go b/internal/peer/common/common.go index 0c875dd5ae0..52c6176a51d 100644 --- a/internal/peer/common/common.go +++ b/internal/peer/common/common.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/golang/protobuf/proto" pcommon "github.com/hyperledger/fabric-protos-go/common" pb "github.com/hyperledger/fabric-protos-go/peer" "github.com/hyperledger/fabric/bccsp" @@ -217,7 +218,7 @@ func GetOrdererEndpointOfChain(chainID string, signer Signer, endorserClient pb. ChaincodeSpec: &pb.ChaincodeSpec{ Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value["GOLANG"]), ChaincodeId: &pb.ChaincodeID{Name: "cscc"}, - Input: &pb.ChaincodeInput{Args: [][]byte{[]byte(cscc.GetConfigBlock), []byte(chainID)}}, + Input: &pb.ChaincodeInput{Args: [][]byte{[]byte(cscc.GetChannelConfig), []byte(chainID)}}, }, } @@ -228,40 +229,36 @@ func GetOrdererEndpointOfChain(chainID string, signer Signer, endorserClient pb. prop, _, err := protoutil.CreateProposalFromCIS(pcommon.HeaderType_CONFIG, "", invocation, creator) if err != nil { - return nil, errors.WithMessage(err, "error creating GetConfigBlock proposal") + return nil, errors.WithMessage(err, "error creating GetChannelConfig proposal") } signedProp, err := protoutil.GetSignedProposal(prop, signer) if err != nil { - return nil, errors.WithMessage(err, "error creating signed GetConfigBlock proposal") + return nil, errors.WithMessage(err, "error creating signed GetChannelConfig proposal") } proposalResp, err := endorserClient.ProcessProposal(context.Background(), signedProp) if err != nil { - return nil, errors.WithMessage(err, "error endorsing GetConfigBlock") + return nil, errors.WithMessage(err, "error endorsing GetChannelConfig") } if proposalResp == nil { - return nil, errors.WithMessage(err, "error nil proposal response") + return nil, errors.New("received nil proposal response") } if proposalResp.Response.Status != 0 && proposalResp.Response.Status != 200 { return nil, errors.Errorf("error bad proposal response %d: %s", proposalResp.Response.Status, proposalResp.Response.Message) } - // parse config block - block, err := protoutil.UnmarshalBlock(proposalResp.Response.Payload) - if err != nil { - return nil, errors.WithMessage(err, "error unmarshaling config block") + // parse config + channelConfig := &pcommon.Config{} + if err := proto.Unmarshal(proposalResp.Response.Payload, channelConfig); err != nil { + return nil, errors.WithMessage(err, "error unmarshaling channel config") } - envelopeConfig, err := protoutil.ExtractEnvelope(block, 0) - if err != nil { - return nil, errors.WithMessage(err, "error extracting config block envelope") - } - bundle, err := channelconfig.NewBundleFromEnvelope(envelopeConfig, cryptoProvider) + bundle, err := channelconfig.NewBundle(chainID, channelConfig, cryptoProvider) if err != nil { - return nil, errors.WithMessage(err, "error loading config block") + return nil, errors.WithMessage(err, "error loading channel config") } return bundle.ChannelConfig().OrdererAddresses(), nil diff --git a/internal/peer/common/common_test.go b/internal/peer/common/common_test.go index e78cdd00d7a..c9b6d38a41c 100644 --- a/internal/peer/common/common_test.go +++ b/internal/peer/common/common_test.go @@ -16,13 +16,22 @@ import ( "strings" "testing" + cb "github.com/hyperledger/fabric-protos-go/common" + pb "github.com/hyperledger/fabric-protos-go/peer" "github.com/hyperledger/fabric/bccsp/factory" "github.com/hyperledger/fabric/bccsp/pkcs11" + "github.com/hyperledger/fabric/bccsp/sw" + "github.com/hyperledger/fabric/common/channelconfig" "github.com/hyperledger/fabric/common/flogging" "github.com/hyperledger/fabric/common/util" "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/peer/common" "github.com/hyperledger/fabric/msp" + msptesttools "github.com/hyperledger/fabric/msp/mgmt/testtools" + "github.com/hyperledger/fabric/protoutil" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -290,6 +299,93 @@ func TestSetBCCSPConfigOverrides(t *testing.T) { }) } +func TestGetOrdererEndpointFromConfigTx(t *testing.T) { + require.NoError(t, msptesttools.LoadMSPSetupForTesting()) + signer, err := common.GetDefaultSigner() + require.NoError(t, err) + factory.InitFactories(nil) + cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) + require.NoError(t, err) + + t.Run("green-path", func(t *testing.T) { + profile := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir()) + channelGroup, err := encoder.NewChannelGroup(profile) + require.NoError(t, err) + channelConfig := &cb.Config{ChannelGroup: channelGroup} + + ordererAddresses := channelconfig.OrdererAddressesValue([]string{"order-1-endpoint", "order-2-end-point"}) + channelConfig.ChannelGroup.Values[ordererAddresses.Key()] = &cb.ConfigValue{ + Value: protoutil.MarshalOrPanic(ordererAddresses.Value()), + } + + mockEndorserClient := common.GetMockEndorserClient( + &pb.ProposalResponse{ + Response: &pb.Response{Status: 200, Payload: protoutil.MarshalOrPanic(channelConfig)}, + Endorsement: &pb.Endorsement{}, + }, + nil, + ) + + ordererEndpoints, err := common.GetOrdererEndpointOfChain("test-channel", signer, mockEndorserClient, cryptoProvider) + require.NoError(t, err) + require.Equal(t, []string{"order-1-endpoint", "order-2-end-point"}, ordererEndpoints) + }) + + t.Run("error-invoking-CSCC", func(t *testing.T) { + mockEndorserClient := common.GetMockEndorserClient( + nil, + errors.Errorf("cscc-invocation-error"), + ) + _, err := common.GetOrdererEndpointOfChain("test-channel", signer, mockEndorserClient, cryptoProvider) + require.EqualError(t, err, "error endorsing GetChannelConfig: cscc-invocation-error") + }) + + t.Run("nil-response", func(t *testing.T) { + mockEndorserClient := common.GetMockEndorserClient( + nil, + nil, + ) + _, err := common.GetOrdererEndpointOfChain("test-channel", signer, mockEndorserClient, cryptoProvider) + require.EqualError(t, err, "received nil proposal response") + }) + + t.Run("bad-status-code-from-cscc", func(t *testing.T) { + mockEndorserClient := common.GetMockEndorserClient( + &pb.ProposalResponse{ + Response: &pb.Response{Status: 404, Payload: []byte{}}, + Endorsement: &pb.Endorsement{}, + }, + nil, + ) + _, err := common.GetOrdererEndpointOfChain("test-channel", signer, mockEndorserClient, cryptoProvider) + require.EqualError(t, err, "error bad proposal response 404: ") + }) + + t.Run("unmarshalable-config", func(t *testing.T) { + mockEndorserClient := common.GetMockEndorserClient( + &pb.ProposalResponse{ + Response: &pb.Response{Status: 200, Payload: []byte("unmarshalable-config")}, + Endorsement: &pb.Endorsement{}, + }, + nil, + ) + _, err := common.GetOrdererEndpointOfChain("test-channel", signer, mockEndorserClient, cryptoProvider) + require.EqualError(t, err, "error unmarshaling channel config: unexpected EOF") + }) + + t.Run("unloadable-config", func(t *testing.T) { + mockEndorserClient := common.GetMockEndorserClient( + &pb.ProposalResponse{ + Response: &pb.Response{Status: 200, Payload: []byte{}}, + Endorsement: &pb.Endorsement{}, + }, + nil, + ) + _, err := common.GetOrdererEndpointOfChain("test-channel", signer, mockEndorserClient, cryptoProvider) + require.EqualError(t, err, "error loading channel config: config must contain a channel group") + }) +} + func setBCCSPEnvVariables(bccspConfig *factory.FactoryOpts) (cleanup func()) { os.Setenv("CORE_PEER_BCCSP_DEFAULT", bccspConfig.Default) os.Setenv("CORE_PEER_BCCSP_SW_SECURITY", strconv.Itoa(bccspConfig.SW.Security)) diff --git a/sampleconfig/configtx.yaml b/sampleconfig/configtx.yaml index e7c526481f7..dc24dc28264 100644 --- a/sampleconfig/configtx.yaml +++ b/sampleconfig/configtx.yaml @@ -194,6 +194,9 @@ Application: &ApplicationDefaults # ACL policy for cscc's "GetConfigBlock" function cscc/GetConfigBlock: /Channel/Application/Readers + # ACL policy for cscc's "GetChannelConfig" function + cscc/GetChannelConfig: /Channel/Application/Readers + #---Miscellaneous peer function to policy mapping for access control---# # ACL policy for invoking chaincodes on peer