Skip to content

Commit

Permalink
Add function GetChannelConfig in cscc and use in peer client
Browse files Browse the repository at this point in the history
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 <manish.sethi@gmail.com>
  • Loading branch information
manish-sethi authored and denyeart committed Oct 29, 2020
1 parent 8758bde commit dd22478
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 62 deletions.
1 change: 1 addition & 0 deletions core/aclmgmt/defaultaclprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions core/aclmgmt/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions core/scc/cscc/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const (
JoinChainBySnapshot string = "JoinChainBySnapshot"
JoinBySnapshotStatus string = "JoinBySnapshotStatus"
GetConfigBlock string = "GetConfigBlock"
GetChannelConfig string = "GetChannelConfig"
GetChannels string = "GetChannels"
)

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
94 changes: 94 additions & 0 deletions core/scc/cscc/configure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions integration/e2e/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ var _ = Describe("EndToEndACL", func() {
// cscc
//
ItEnforcesPolicy("cscc", "GetConfigBlock", "testchannel")
ItEnforcesPolicy("cscc", "GetChannelConfig", "testchannel")

//
// _lifecycle ACL policies
Expand Down
47 changes: 0 additions & 47 deletions internal/peer/chaincode/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
27 changes: 12 additions & 15 deletions internal/peer/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)}},
},
}

Expand All @@ -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
Expand Down
Loading

0 comments on commit dd22478

Please sign in to comment.