From 48933484880d4a26644cee48880144d5b5680529 Mon Sep 17 00:00:00 2001 From: VRamakrishna Date: Tue, 21 Nov 2023 07:42:18 +0000 Subject: [PATCH] feat(weaver-go): upgraded Weaver Fabric Go SDK with membership functions Added membership management functions for recording and lookup to Weaver Fabric Go SDK. Fixed issues with the membership management functions in the Weaver Fabric Node SDK. Updated the Weaver TS protobuf builder to generate output with proper imports. (The above was required as the "grpc" dependency had been removed from the "package.json.") Updated instructions in readme and docs. Signed-off-by: VRamakrishna Signed-off-by: Sandeep Nishad --- .../enabling-weaver-network/fabric.md | 3 +- weaver/common/protos-js/build-protos.sh | 8 +- .../contracts/interop/membership.go | 2 + .../enabling-weaver-network/fabric.md | 3 +- .../interop-setup/configure-network.ts | 6 +- .../sdks/fabric/go-sdk/decoders/decoders.go | 19 +- weaver/sdks/fabric/go-sdk/go.mod | 16 +- weaver/sdks/fabric/go-sdk/go.sum | 39 +- .../interoperable-helper.go | 2 +- .../membershipmanager/membership_manager.go | 667 ++++++++++++++++++ weaver/sdks/fabric/go-sdk/readme.md | 45 ++ .../src/MembershipManager.ts | 14 +- 12 files changed, 778 insertions(+), 46 deletions(-) create mode 100644 weaver/sdks/fabric/go-sdk/membershipmanager/membership_manager.go diff --git a/docs/docs/weaver/getting-started/enabling-weaver-network/fabric.md b/docs/docs/weaver/getting-started/enabling-weaver-network/fabric.md index 81c0543f4b..89e085696d 100644 --- a/docs/docs/weaver/getting-started/enabling-weaver-network/fabric.md +++ b/docs/docs/weaver/getting-started/enabling-weaver-network/fabric.md @@ -308,13 +308,12 @@ A Fabric network channel must share its security domain (or membership) configur const response = await MembershipManager.createLocalMembership( gateway, members, // list of all organization MSPIDs that are part of the channel - securityDomain, // name of the local network's security domain channelName, // Channel Name contractName // Fabric Interoperation Chaincode installation ID on the channel ) } catch (e) { // On error try updating local membership - const response = await MembershipManager.updateLocalMembership(gateway, members, securityDomain, channelName, contractName) + const response = await MembershipManager.updateLocalMembership(gateway, members, channelName, contractName) } ``` diff --git a/weaver/common/protos-js/build-protos.sh b/weaver/common/protos-js/build-protos.sh index 556e000112..38d4f93aa7 100755 --- a/weaver/common/protos-js/build-protos.sh +++ b/weaver/common/protos-js/build-protos.sh @@ -23,9 +23,9 @@ grpc_tools_node_protoc --proto_path=$PROTOSDIR --proto_path=$FABRIC_PROTOSDIR - # Typescript Build protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/common/interop_payload.proto $PROTOSDIR/common/asset_locks.proto $PROTOSDIR/common/asset_transfer.proto $PROTOSDIR/common/ack.proto $PROTOSDIR/common/query.proto $PROTOSDIR/common/state.proto $PROTOSDIR/common/proofs.proto $PROTOSDIR/common/verification_policy.proto $PROTOSDIR/common/membership.proto $PROTOSDIR/common/access_control.proto $PROTOSDIR/common/events.proto protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/corda/view_data.proto -protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/driver/driver.proto +protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/driver/driver.proto protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/fabric/view_data.proto -protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/identity/agent.proto -protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/networks/networks.proto -protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/relay/datatransfer.proto $PROTOSDIR/relay/events.proto +protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/identity/agent.proto +protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/networks/networks.proto +protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $PROTOSDIR/relay/datatransfer.proto $PROTOSDIR/relay/events.proto protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=$BUILDDIR -I $PROTOSDIR -I $FABRIC_PROTOSDIR $FABRIC_PROTOSDIR/msp/identities.proto $FABRIC_PROTOSDIR/peer/proposal_response.proto $FABRIC_PROTOSDIR/peer/proposal.proto $FABRIC_PROTOSDIR/peer/chaincode.proto $FABRIC_PROTOSDIR/common/policies.proto $FABRIC_PROTOSDIR/msp/msp_principal.proto diff --git a/weaver/core/network/fabric-interop-cc/contracts/interop/membership.go b/weaver/core/network/fabric-interop-cc/contracts/interop/membership.go index bae0ec6d87..6ad8978f12 100644 --- a/weaver/core/network/fabric-interop-cc/contracts/interop/membership.go +++ b/weaver/core/network/fabric-interop-cc/contracts/interop/membership.go @@ -183,6 +183,7 @@ func (s *SmartContract) CreateLocalMembership(ctx contractapi.TransactionContext if err != nil { return fmt.Errorf("Unmarshal error: %s", err) } + membership.SecurityDomain = membershipLocalSecurityDomain membershipLocalKey, err := ctx.GetStub().CreateCompositeKey(membershipObjectType, []string{membershipLocalSecurityDomain}) acp, getErr := ctx.GetStub().GetState(membershipLocalKey) @@ -220,6 +221,7 @@ func (s *SmartContract) UpdateLocalMembership(ctx contractapi.TransactionContext if err != nil { return fmt.Errorf("Unmarshal error: %s", err) } + membership.SecurityDomain = membershipLocalSecurityDomain membershipLocalKey, err := ctx.GetStub().CreateCompositeKey(membershipObjectType, []string{membershipLocalSecurityDomain}) _, getErr := s.GetMembershipBySecurityDomain(ctx, membershipLocalSecurityDomain) diff --git a/weaver/docs/docs/external/getting-started/enabling-weaver-network/fabric.md b/weaver/docs/docs/external/getting-started/enabling-weaver-network/fabric.md index 3f5aad5a78..55b052d83c 100644 --- a/weaver/docs/docs/external/getting-started/enabling-weaver-network/fabric.md +++ b/weaver/docs/docs/external/getting-started/enabling-weaver-network/fabric.md @@ -304,13 +304,12 @@ A Fabric network channel must share its security domain (or membership) configur const response = await MembershipManager.createLocalMembership( gateway, members, // list of all organization MSPIDs that are part of the channel - securityDomain, // name of the local network's security domain channelName, // Channel Name contractName // Fabric Interoperation Chaincode installation ID on the channel ) } catch (e) { // On error try updating local membership - const response = await MembershipManager.updateLocalMembership(gateway, members, securityDomain, channelName, contractName) + const response = await MembershipManager.updateLocalMembership(gateway, members, channelName, contractName) } ``` - `` should be replaced with standard (boilerplate) code to get a handle to your network's gateway. This requires a special wallet identity, namely one with a `network-admin` attribute indicating that the caller is a trusted network administrator who is authorized to record local memberships on the `channelName` channel. diff --git a/weaver/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts b/weaver/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts index b85bbc1db6..3a33ad4ea5 100644 --- a/weaver/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts +++ b/weaver/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts @@ -142,13 +142,13 @@ const loadLocalHelper = async ( registerUser: false }) try { - const response = await MembershipManager.createLocalMembership(gateway, members, networkName, channelName, contractName) + const response = await MembershipManager.createLocalMembership(gateway, members, channelName, contractName) logger.info('CreateLocalMembership Successful.') } catch (e) { logger.error(e) logger.info('CreateLocalMembership attempting Update') - const response = await MembershipManager.updateLocalMembership(gateway, members, networkName, channelName, contractName) - logger.info('Update Local Memebrship response: success: ', response) + const response = await MembershipManager.updateLocalMembership(gateway, members, channelName, contractName) + logger.info('UpdateLocalMembership response: success: ', response) } } diff --git a/weaver/sdks/fabric/go-sdk/decoders/decoders.go b/weaver/sdks/fabric/go-sdk/decoders/decoders.go index 77a1edef3b..8eb7e9c1ed 100644 --- a/weaver/sdks/fabric/go-sdk/decoders/decoders.go +++ b/weaver/sdks/fabric/go-sdk/decoders/decoders.go @@ -13,8 +13,7 @@ import ( "fmt" "github.com/golang/protobuf/proto" - "github.com/hyperledger/fabric-protos-go/peer" - pb "github.com/hyperledger/fabric-protos-go/peer" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" log "github.com/sirupsen/logrus" ) @@ -25,8 +24,8 @@ func logThenErrorf(format string, args ...interface{}) error { return errors.New(errorMsg) } -func DeserializeRemoteProposal(proposalBytes []byte) (*pb.Proposal, error) { - proposal := &pb.Proposal{} +func DeserializeRemoteProposal(proposalBytes []byte) (*peer.Proposal, error) { + proposal := &peer.Proposal{} err := proto.Unmarshal(proposalBytes, proposal) if err != nil { return proposal, logThenErrorf(err.Error()) @@ -35,8 +34,8 @@ func DeserializeRemoteProposal(proposalBytes []byte) (*pb.Proposal, error) { return proposal, nil } -func DeserializeRemoteProposalResponse(proposalResponseBytes []byte) (*pb.ProposalResponse, error) { - proposalResponse := &pb.ProposalResponse{} +func DeserializeRemoteProposalResponse(proposalResponseBytes []byte) (*peer.ProposalResponse, error) { + proposalResponse := &peer.ProposalResponse{} err := proto.Unmarshal(proposalResponseBytes, proposalResponse) if err != nil { return proposalResponse, logThenErrorf(err.Error()) @@ -45,7 +44,7 @@ func DeserializeRemoteProposalResponse(proposalResponseBytes []byte) (*pb.Propos return proposalResponse, nil } -func DeserializeRemoteProposalHex(proposalBytesHex []byte) (*pb.Proposal, error) { +func DeserializeRemoteProposalHex(proposalBytesHex []byte) (*peer.Proposal, error) { proposalBytes, err := hex.DecodeString(string(proposalBytesHex)) if err != nil { return nil, logThenErrorf("cannot decode 'hex' string (%s) to bytes error: %s", proposalBytesHex, err.Error()) @@ -54,7 +53,7 @@ func DeserializeRemoteProposalHex(proposalBytesHex []byte) (*pb.Proposal, error) return DeserializeRemoteProposal(proposalBytes) } -func DeserializeRemoteProposalResponseHex(proposalResponseBytesHex []byte) (*pb.ProposalResponse, error) { +func DeserializeRemoteProposalResponseHex(proposalResponseBytesHex []byte) (*peer.ProposalResponse, error) { proposalResponseBytes, err := hex.DecodeString(string(proposalResponseBytesHex)) if err != nil { return nil, logThenErrorf("cannot decode 'hex' string (%s) to bytes error: %s", proposalResponseBytesHex, err.Error()) @@ -63,13 +62,13 @@ func DeserializeRemoteProposalResponseHex(proposalResponseBytesHex []byte) (*pb. return DeserializeRemoteProposalResponse(proposalResponseBytes) } -func DeserializeRemoteProposalBase64(proposalBytesBase64 []byte) (*pb.Proposal, error) { +func DeserializeRemoteProposalBase64(proposalBytesBase64 []byte) (*peer.Proposal, error) { proposalBytes := base64.StdEncoding.EncodeToString(proposalBytesBase64) return DeserializeRemoteProposal([]byte(proposalBytes)) } -func DeserializeRemoteProposalResponseBase64(proposalResponseBytesBase64 []byte) (*pb.ProposalResponse, error) { +func DeserializeRemoteProposalResponseBase64(proposalResponseBytesBase64 []byte) (*peer.ProposalResponse, error) { proposalResponseBytes := base64.StdEncoding.EncodeToString(proposalResponseBytesBase64) return DeserializeRemoteProposalResponse([]byte(proposalResponseBytes)) diff --git a/weaver/sdks/fabric/go-sdk/go.mod b/weaver/sdks/fabric/go-sdk/go.mod index 86ec75c235..6e95fdf664 100644 --- a/weaver/sdks/fabric/go-sdk/go.mod +++ b/weaver/sdks/fabric/go-sdk/go.mod @@ -6,19 +6,23 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.0 github.com/hyperledger/cacti/weaver/common/protos-go/v2 v2.0.0-alpha.2 - github.com/hyperledger/fabric-protos-go v0.3.0 + github.com/hyperledger/fabric-admin-sdk v0.0.0 + github.com/hyperledger/fabric-gateway v1.2.1 + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.1 github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.7.0 - google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.30.0 + github.com/stretchr/testify v1.8.1 + google.golang.org/grpc v1.57.0 + google.golang.org/protobuf v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/hyperledger/fabric-protos-go v0.3.0 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/weaver/sdks/fabric/go-sdk/go.sum b/weaver/sdks/fabric/go-sdk/go.sum index 612474eb93..82c677b7d5 100644 --- a/weaver/sdks/fabric/go-sdk/go.sum +++ b/weaver/sdks/fabric/go-sdk/go.sum @@ -1,25 +1,43 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hyperledger/cacti/weaver/common/protos-go/v2 v2.0.0-alpha.2 h1:RCScZbqnxdX1RDrp4HATGXs8Pbh2yLI6F6ULjAjTUso= github.com/hyperledger/cacti/weaver/common/protos-go/v2 v2.0.0-alpha.2/go.mod h1:3DmkYfZoc+TtcAgF3kX6CmQDNKKKCHgbaoQuYu/3ayc= +github.com/hyperledger/fabric-admin-sdk v0.0.0 h1:SS/qekuUUOzvx1+1UzJCEcHD/UcCDpTxqrCjOVoy1Rg= +github.com/hyperledger/fabric-admin-sdk v0.0.0/go.mod h1:AGAr/kVPWagaEh+bJeieTiajC4pbfcYXVySENqwk2Sc= +github.com/hyperledger/fabric-gateway v1.2.1 h1:K6b7Q+y0x47SQ2TVnLih2mFNQ7/izmdAhnFgiylfSVQ= +github.com/hyperledger/fabric-gateway v1.2.1/go.mod h1:SCuB+RNueO6nOiW7QAyfeh4PaB1d1U4R6WKuq0IG66I= github.com/hyperledger/fabric-protos-go v0.3.0 h1:MXxy44WTMENOh5TI8+PCK2x6pMj47Go2vFRKDHB2PZs= github.com/hyperledger/fabric-protos-go v0.3.0/go.mod h1:WWnyWP40P2roPmmvxsUXSvVI/CF6vwY1K1UFidnKBys= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.1 h1:zAxLu6XxaiOkNFl7iTO54RCc3fGO+5hjJKTqRfvxT1A= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.1/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/onsi/ginkgo/v2 v2.8.2 h1:rWe/YULhcr//R7U3kN11ISrlT7aVPW9K/AbXbGakXqY= +github.com/onsi/gomega v1.27.0 h1:QLidEla4bXUuZVFa4KX6JHCsuGgbi85LC/pCHrt/O08= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -27,17 +45,18 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/weaver/sdks/fabric/go-sdk/interoperablehelper/interoperable-helper.go b/weaver/sdks/fabric/go-sdk/interoperablehelper/interoperable-helper.go index 30041e69cd..20303947aa 100644 --- a/weaver/sdks/fabric/go-sdk/interoperablehelper/interoperable-helper.go +++ b/weaver/sdks/fabric/go-sdk/interoperablehelper/interoperable-helper.go @@ -16,7 +16,7 @@ import ( "github.com/google/uuid" "github.com/golang/protobuf/proto" - "github.com/hyperledger/fabric-protos-go/peer" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/hyperledger/cacti/weaver/common/protos-go/v2/common" "github.com/hyperledger/cacti/weaver/common/protos-go/v2/corda" "github.com/hyperledger/cacti/weaver/common/protos-go/v2/fabric" diff --git a/weaver/sdks/fabric/go-sdk/membershipmanager/membership_manager.go b/weaver/sdks/fabric/go-sdk/membershipmanager/membership_manager.go new file mode 100644 index 0000000000..654be316e5 --- /dev/null +++ b/weaver/sdks/fabric/go-sdk/membershipmanager/membership_manager.go @@ -0,0 +1,667 @@ +/* +Copyright 2020 IBM All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package membershipmanager + +import ( + "context" + "fmt" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "net" + "os" + "strconv" + "strings" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + protoV2 "google.golang.org/protobuf/proto" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + mspprotos "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/hyperledger/fabric-admin-sdk/pkg/channel" + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-gateway/pkg/client" + cidentity "github.com/hyperledger/fabric-gateway/pkg/identity" + + cactiprotos "github.com/hyperledger/cacti/weaver/common/protos-go/v2/common" +) + + +func CreateLocalMembership(walletPath, userName, connectionProfilePath, channelId, weaverCCId string, mspIds []string) error { + membership, err := GetMSPConfigurations(walletPath, userName, connectionProfilePath, channelId, mspIds) + if err != nil { + return err + } + membership.SecurityDomain = "" // We don't need this as the Weaver chaincode will internally use a designated keyword + membershipBytes, err := protoV2.Marshal(membership) + if err != nil { + return err + } + membershipSerialized64 := base64.StdEncoding.EncodeToString(membershipBytes) + + _, err = membershipTx("CreateLocalMembership", walletPath, userName, connectionProfilePath, channelId, weaverCCId, membershipSerialized64, mspIds) + if err != nil { + return err + } + + return nil +} + +func UpdateLocalMembership(walletPath, userName, connectionProfilePath, channelId, weaverCCId string, mspIds []string) error { + membership, err := GetMSPConfigurations(walletPath, userName, connectionProfilePath, channelId, mspIds) + if err != nil { + return err + } + membership.SecurityDomain = "" // We don't need this as the Weaver chaincode will internally use a designated keyword + membershipBytes, err := protoV2.Marshal(membership) + if err != nil { + return err + } + membershipSerialized64 := base64.StdEncoding.EncodeToString(membershipBytes) + + _, err = membershipTx("UpdateLocalMembership", walletPath, userName, connectionProfilePath, channelId, weaverCCId, membershipSerialized64, mspIds) + if err != nil { + return err + } + + return nil +} + +func DeleteLocalMembership(walletPath, userName, connectionProfilePath, channelId, weaverCCId string, mspIds []string) error { + _, err := membershipTx("DeleteLocalMembership", walletPath, userName, connectionProfilePath, channelId, weaverCCId, "", mspIds) + if err != nil { + return err + } + + return nil +} + +func ReadMembership(walletPath, userName, connectionProfilePath, channelId, weaverCCId, securityDomain string, mspIds []string) (string, error) { + result, err := membershipTx("GetMembershipBySecurityDomain", walletPath, userName, connectionProfilePath, channelId, weaverCCId, securityDomain, mspIds) + if err != nil { + return "", err + } + + return string(result), nil +} + +func GetMembershipUnit(walletPath, userName, connectionProfilePath, channelId, mspId string) (*cactiprotos.Member, error) { + configBlock, err := GetConfigBlockFromChannel(walletPath, userName, connectionProfilePath, channelId) + if err != nil { + return nil, err + } + + return GetMembershipForMspIdFromBlock(configBlock, mspId) +} + +func GetMSPConfigurations(walletPath, userName, connectionProfilePath, channelId string, mspIds []string) (*cactiprotos.Membership, error) { + configBlock, err := GetConfigBlockFromChannel(walletPath, userName, connectionProfilePath, channelId) + if err != nil { + return nil, err + } + + return GetMembershipForMspIdsFromBlock(configBlock, mspIds) +} + +func GetAllMSPConfigurations(walletPath, userName, connectionProfilePath, channelId string, ordererMspIds []string) (*cactiprotos.Membership, error) { + configBlock, err := GetConfigBlockFromChannel(walletPath, userName, connectionProfilePath, channelId) + if err != nil { + return nil, err + } + + return GetMembershipForAllMspIdsFromBlock(configBlock, ordererMspIds) +} + +func GetConfigBlockFromChannel(walletPath, userName, connectionProfilePath, channelId string) (*common.Block, error) { + mspId, signCert, signKey, timeout, connection, err := getNetworkConnectionAndInfo(walletPath, userName, connectionProfilePath) + if err != nil { + return nil, err + } + defer connection.Close() + + // Client identity used to carry out deployment tasks. + signCertParsed, err := readCertificate(signCert) + if err != nil { + return nil, err + } + signKeyParsed, err := readPrivateKey(signKey) + if err != nil { + return nil, err + } + clientId, err := identity.NewPrivateKeySigningIdentity(mspId, signCertParsed, signKeyParsed) + if err != nil { + return nil, err + } + + // Context used to manage Fabric invocations. + seconds, err := time.ParseDuration(strconv.Itoa(timeout) + "s") + if err != nil { + return nil, err + } + ctx, cancel := context.WithTimeout(context.Background(), seconds*time.Second) + defer cancel() + + configBlock, err := channel.GetConfigBlock(ctx, connection, clientId, channelId) + if err != nil { + return nil, err + } + + return configBlock, err +} + +func GetMembershipForMspIdFromBlock(block *common.Block, mspId string) (*cactiprotos.Member, error) { + var envelope common.Envelope + err := protoV2.Unmarshal((block.GetData().GetData())[0], &envelope) + if err != nil { + return nil, err + } + + var payload common.Payload + err = protoV2.Unmarshal(envelope.GetPayload(), &payload) + if err != nil { + return nil, err + } + + var channelHeader common.ChannelHeader + err = protoV2.Unmarshal(payload.GetHeader().GetChannelHeader(), &channelHeader) + if err != nil { + return nil, err + } + + if common.HeaderType(channelHeader.GetType()) == common.HeaderType_CONFIG { + var configEnvelope common.ConfigEnvelope + err = protoV2.Unmarshal(payload.GetData(), &configEnvelope) + if err != nil { + return nil, err + } + + for _, group := range configEnvelope.GetConfig().GetChannelGroup().GetGroups()["Application"].GetGroups() { + var mspConfig mspprotos.MSPConfig + groupValMsp, ok := group.GetValues()["MSP"] + if !ok { + fmt.Println("Warning: Channel Application group has no 'MSP' key") + continue + } + err = protoV2.Unmarshal(groupValMsp.GetValue(), &mspConfig) + if err != nil { + return nil, err + } + + // Ideally, we would replace the '0' in the below conditional with 'int32(msp.FABRIC)' + // according to https://pkg.go.dev/github.com/hyperledger/fabric@v2.1.1+incompatible/msp#ProviderType + // but this would require importing "github.com/hyperledger/fabric/msp", + // which depends on 'fabric-protos-go', which in turn conflicts with 'fabric-protos-go-apiv2', + // which is imported by this module. + if mspConfig.GetType() == 0 { + var fabricMspConfig mspprotos.FabricMSPConfig + err = protoV2.Unmarshal(mspConfig.GetConfig(), &fabricMspConfig) + if err != nil { + return nil, err + } + + if fabricMspConfig.GetName() == mspId { + memberUnit := &cactiprotos.Member{} + memberUnit.Type = "certificate" + memberUnit.Value = "" + memberUnit.Chain = []string{} + for _, certBytes := range fabricMspConfig.GetRootCerts() { + memberUnit.Chain = append(memberUnit.Chain, string(certBytes)) + } + for _, certBytes := range fabricMspConfig.GetIntermediateCerts() { + memberUnit.Chain = append(memberUnit.Chain, string(certBytes)) + } + return memberUnit, nil + } + } + } + } + + return nil, nil +} + +func GetMembershipForMspIdsFromBlock(block *common.Block, mspIds []string) (*cactiprotos.Membership, error) { + // Convert slice to map + var mspMap = make(map[string]bool) + for _, mspId := range mspIds { + mspMap[mspId] = true + } + + var envelope common.Envelope + err := protoV2.Unmarshal((block.GetData().GetData())[0], &envelope) + if err != nil { + return nil, err + } + + var payload common.Payload + err = protoV2.Unmarshal(envelope.GetPayload(), &payload) + if err != nil { + return nil, err + } + + var channelHeader common.ChannelHeader + err = protoV2.Unmarshal(payload.GetHeader().GetChannelHeader(), &channelHeader) + if err != nil { + return nil, err + } + + membership := &cactiprotos.Membership{} + membership.Members = make(map[string]*cactiprotos.Member) + + if common.HeaderType(channelHeader.GetType()) == common.HeaderType_CONFIG { + var configEnvelope common.ConfigEnvelope + err = protoV2.Unmarshal(payload.GetData(), &configEnvelope) + if err != nil { + return nil, err + } + + for _, group := range configEnvelope.GetConfig().GetChannelGroup().GetGroups()["Application"].GetGroups() { + var mspConfig mspprotos.MSPConfig + groupValMsp, ok := group.GetValues()["MSP"] + if !ok { + fmt.Println("Warning: Channel Application group has no 'MSP' key") + continue + } + err = protoV2.Unmarshal(groupValMsp.GetValue(), &mspConfig) + if err != nil { + return nil, err + } + + // Ideally, we would replace the '0' in the below conditional with 'int32(msp.FABRIC)' + // according to https://pkg.go.dev/github.com/hyperledger/fabric@v2.1.1+incompatible/msp#ProviderType + // but this would require importing "github.com/hyperledger/fabric/msp", + // which depends on 'fabric-protos-go', which in turn conflicts with 'fabric-protos-go-apiv2', + // which is imported by this module. + if mspConfig.GetType() == 0 { + var fabricMspConfig mspprotos.FabricMSPConfig + err = protoV2.Unmarshal(mspConfig.GetConfig(), &fabricMspConfig) + if err != nil { + return nil, err + } + + if mspMap[fabricMspConfig.GetName()] == true { + memberUnit := &cactiprotos.Member{} + memberUnit.Type = "certificate" + memberUnit.Value = "" + memberUnit.Chain = []string{} + for _, certBytes := range fabricMspConfig.GetRootCerts() { + memberUnit.Chain = append(memberUnit.Chain, string(certBytes)) + } + for _, certBytes := range fabricMspConfig.GetIntermediateCerts() { + memberUnit.Chain = append(memberUnit.Chain, string(certBytes)) + } + membership.Members[fabricMspConfig.GetName()] = memberUnit + } + } + } + } + + return membership, nil +} + +func GetMembershipForAllMspIdsFromBlock(block *common.Block, ordererMspIds []string) (*cactiprotos.Membership, error) { + // Convert slice to map + var ordererMspMap = make(map[string]bool) + for _, mspId := range ordererMspIds { + ordererMspMap[mspId] = true + } + + var envelope common.Envelope + err := protoV2.Unmarshal((block.GetData().GetData())[0], &envelope) + if err != nil { + return nil, err + } + + var payload common.Payload + err = protoV2.Unmarshal(envelope.GetPayload(), &payload) + if err != nil { + return nil, err + } + + var channelHeader common.ChannelHeader + err = protoV2.Unmarshal(payload.GetHeader().GetChannelHeader(), &channelHeader) + if err != nil { + return nil, err + } + + membership := &cactiprotos.Membership{} + membership.Members = make(map[string]*cactiprotos.Member) + + if common.HeaderType(channelHeader.GetType()) == common.HeaderType_CONFIG { + var configEnvelope common.ConfigEnvelope + err = protoV2.Unmarshal(payload.GetData(), &configEnvelope) + if err != nil { + return nil, err + } + + for _, group := range configEnvelope.GetConfig().GetChannelGroup().GetGroups()["Application"].GetGroups() { + var mspConfig mspprotos.MSPConfig + groupValMsp, ok := group.GetValues()["MSP"] + if !ok { + fmt.Println("Warning: Channel Application group has no 'MSP' key") + continue + } + err = protoV2.Unmarshal(groupValMsp.GetValue(), &mspConfig) + if err != nil { + return nil, err + } + + // Ideally, we would replace the '0' in the below conditional with 'int32(msp.FABRIC)' + // according to https://pkg.go.dev/github.com/hyperledger/fabric@v2.1.1+incompatible/msp#ProviderType + // but this would require importing "github.com/hyperledger/fabric/msp", + // which depends on 'fabric-protos-go', which in turn conflicts with 'fabric-protos-go-apiv2', + // which is imported by this module. + if mspConfig.GetType() == 0 { + var fabricMspConfig mspprotos.FabricMSPConfig + err = protoV2.Unmarshal(mspConfig.GetConfig(), &fabricMspConfig) + if err != nil { + return nil, err + } + + if _, isOrdererMspId := ordererMspMap[fabricMspConfig.GetName()]; !isOrdererMspId { + memberUnit := &cactiprotos.Member{} + memberUnit.Type = "certificate" + memberUnit.Value = "" + memberUnit.Chain = []string{} + for _, certBytes := range fabricMspConfig.GetRootCerts() { + memberUnit.Chain = append(memberUnit.Chain, string(certBytes)) + } + for _, certBytes := range fabricMspConfig.GetIntermediateCerts() { + memberUnit.Chain = append(memberUnit.Chain, string(certBytes)) + } + membership.Members[fabricMspConfig.GetName()] = memberUnit + } + } + } + } + + return membership, nil +} + +func membershipTx(txFunc, walletPath, userName, connectionProfilePath, channelId, weaverCCId, ccArg string, mspIds []string) ([]byte, error) { + mspId, signCert, signKey, _, connection, err := getNetworkConnectionAndInfo(walletPath, userName, connectionProfilePath) + if err != nil { + return nil, err + } + defer connection.Close() + + id, err := newIdentity(signCert, mspId) + if err != nil { + return nil, err + } + + sign, err := newSign(signKey) + if err != nil { + return nil, err + } + + // Instantiate the network gateway + gateway, err := client.Connect(id, client.WithSign(sign), client.WithClientConnection(connection)) + if err != nil { + return nil, err + } + defer gateway.Close() + + network := gateway.GetNetwork(channelId) + weaverCC := network.GetContract(weaverCCId) + if ccArg == "" { + return weaverCC.SubmitTransaction(txFunc) + } else { + return weaverCC.SubmitTransaction(txFunc, ccArg) + } +} + +func getNetworkConnectionAndInfo(walletPath, userName, connectionProfilePath string) (string, string, string, int, *grpc.ClientConn, error) { + // gRPC connection to a target peer. + mspId, signCert, signKey, err := getInfoFromWallet(walletPath, userName) + if err != nil { + return "", "", "", -1, nil, err + } + peerEndpoint, tlsCaCert, timeout, err := getInfoFromConnectionProfile(connectionProfilePath, mspId) + if err != nil { + return "", "", "", -1, nil, err + } + connection, err := newGrpcConnection(peerEndpoint, tlsCaCert) + if err != nil { + return "", "", "", -1, nil, err + } + + return mspId, signCert, signKey, timeout, connection, nil +} + +// newIdentity creates a client identity for this Gateway connection using an X.509 certificate. +func newIdentity(certificatePEM, mspId string) (*cidentity.X509Identity, error) { + certificate, err := cidentity.CertificateFromPEM([]byte(certificatePEM)) + if err != nil { + return nil, err + } + + return cidentity.NewX509Identity(mspId, certificate) +} + +// newSign creates a function that generates a digital signature from a message digest using a private key. +func newSign(privateKeyPEM string) (cidentity.Sign, error) { + privateKey, err := cidentity.PrivateKeyFromPEM([]byte(privateKeyPEM)) + if err != nil { + return nil, err + } + + return cidentity.NewPrivateKeySign(privateKey) +} + +func getInfoFromWallet(walletPath, userName string) (string, string, string, error) { + walletUserId, err := os.ReadFile(walletPath + "/" + userName + ".id") + if err != nil { + return "", "", "", err + } + + var walletId = map[string]interface{}{} + err = json.Unmarshal(walletUserId, &walletId) + if err != nil { + return "", "", "", err + } + + walletCredentialsIface, ok := walletId["credentials"] + if !ok { + return "", "", "", fmt.Errorf("Wallet Id has no 'credentials' attribute") + } + var walletCredentials = walletCredentialsIface.(map[string]interface{}) + mspId, ok := walletId["mspId"] + if !ok { + return "", "", "", fmt.Errorf("Wallet Id has no 'mspId' attribute") + } + certificate, ok := walletCredentials["certificate"] + if !ok { + return "", "", "", fmt.Errorf("Wallet Id has no 'credentials.certificate' attribute") + } + privateKey, ok := walletCredentials["privateKey"] + if !ok { + return "", "", "", fmt.Errorf("Wallet Id has no 'credentials.privateKey' attribute") + } + + return mspId.(string), certificate.(string), privateKey.(string), nil +} + +func getInfoFromConnectionProfile(connectionProfilePath, mspId string) (string, string, int, error) { + connectionProfile, err := os.ReadFile(connectionProfilePath) + if err != nil { + return "", "", -1, err + } + + var connProfile = map[string]interface{}{} + err = json.Unmarshal(connectionProfile, &connProfile) + if err != nil { + return "", "", -1, err + } + + timeoutVal := 300 // peer connection timeout in seconds + // Get endorser timeout from the "client.connection.timeout.peer.endorser" attribute + clientIface, ok := connProfile["client"] + if !ok { + fmt.Println("Warning: Connection profile has no 'client' attribute") + } else { + var client = clientIface.(map[string]interface{}) + connectionIface, ok := client["connection"] + if !ok { + fmt.Println("Warning: Connection profile has no 'client.connection' attribute") + } else { + var connection = connectionIface.(map[string]interface{}) + timeoutIface, ok := connection["timeout"] + if !ok { + fmt.Println("Warning: Connection profile has no 'client.connection.timeout' attribute") + } else { + var timeout = timeoutIface.(map[string]interface{}) + tpeerIface, ok := timeout["peer"] + if !ok { + fmt.Println("Warning: Connection profile has no 'client.connection.timeout.peer' attribute") + } else { + var tpeer = tpeerIface.(map[string]interface{}) + endorserIface, ok := tpeer["endorser"] + if !ok { + fmt.Println("Warning: Connection profile has no 'client.connection.timeout.peer.endorser' attribute") + } else { + var endorser = endorserIface.(string) + profileTimeoutVal, err := strconv.Atoi(endorser) + if err != nil { + fmt.Printf("Warning: Cannot parse '%s'. Using default timeout` '%d'\n", endorser, timeoutVal) + } + timeoutVal = profileTimeoutVal + } + } + } + } + } + + orgsIface, ok := connProfile["organizations"] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no 'organizations' attribute") + } + var orgs = orgsIface.(map[string]interface{}) + peersIface, ok := connProfile["peers"] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no 'peers' attribute") + } + var peers = peersIface.(map[string]interface{}) + for orgName, orgInfo := range orgs { + var org = orgInfo.(map[string]interface{}) + orgMspId, ok := org["mspid"] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no 'mspId' attribute for the '%s' organization", orgName) + } + if orgMspId == mspId { + orgPeers := org["peers"].([]interface{}) + if len(orgPeers) == 0 { + return "", "", -1, fmt.Errorf("No peers in connection profile for org with MSP ID %s", mspId) + } else { + peerId := orgPeers[0].(string) + peerInfo, ok := peers[peerId] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no peer '%s' info", peerId) + } + var peer = peerInfo.(map[string]interface{}) + tlsCACertsIface, ok := peer["tlsCACerts"] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no 'tlsCACerts' attribute for the '%s' peer", peerId) + } + var tlsCACerts = tlsCACertsIface.(map[string]interface{}) + peerUrlIface, ok := peer["url"] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no 'url' attribute for the '%s' peer", peerId) + } + peerUrl := peerUrlIface.(string) + tlsCACertsPemIface, ok := tlsCACerts["pem"] + if !ok { + return "", "", -1, fmt.Errorf("Connection profile has no 'tlsCACerts.pem' attribute for the '%s' peer", peerId) + } + peerUrlParts := strings.Split(peerUrl, "://") + if len(peerUrlParts) == 1 { + return peerUrlParts[0], tlsCACertsPemIface.(string), timeoutVal, nil + } else { // If not 1, the slice must have a length larger than 1 + return peerUrlParts[1], tlsCACertsPemIface.(string), timeoutVal, nil + } + } + } + } + + return "", "", -1, fmt.Errorf("Unable to find required info in connection profile") +} + +func newGrpcConnection(networkPeerEndpoint, tlsCACertPEM string) (*grpc.ClientConn, error) { + tlsCACert, err := readCertificate(tlsCACertPEM) + if err != nil { + return nil, err + } + certPool := x509.NewCertPool() + certPool.AddCert(tlsCACert) + transportCredentials := credentials.NewClientTLSFromCert(certPool, "") + + networkPeerEndpointParts := strings.Split(networkPeerEndpoint, ":") + hostname := networkPeerEndpointParts[0] + port := networkPeerEndpointParts[1] + addresses, err := net.LookupHost(hostname) + + var connection *grpc.ClientConn + if err != nil || len(addresses) == 0 { + fmt.Printf("Warning: Cannot resolve supplied hostname '%s'. Using 'localhost' instead.\n", hostname) + connection, err = grpc.Dial("localhost:" + port, grpc.WithTransportCredentials(transportCredentials)) + } else { + connection, err = grpc.Dial(networkPeerEndpoint, grpc.WithTransportCredentials(transportCredentials)) + } + if err != nil { + return nil, err + } + + return connection, nil +} + +func readCertificateFile(certFile string) (*x509.Certificate, error) { + certificatePEM, err := os.ReadFile(certFile) + if err != nil { + return nil, err + } + + return readCertificate(string(certificatePEM)) +} + +func readCertificate(certificatePEM string) (*x509.Certificate, error) { + block, _ := pem.Decode([]byte(certificatePEM)) + if block == nil { + return nil, fmt.Errorf("failed to parse certificate PEM") + } + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %s", err.Error()) + } + + return certificate, nil +} + +func readPrivateKeyFile(keyFile string) (crypto.PrivateKey, error) { + privateKeyPEM, err := os.ReadFile(keyFile) + if err != nil { + return nil, err + } + + return readPrivateKey(string(privateKeyPEM)) +} + +func readPrivateKey(privateKeyPEM string) (crypto.PrivateKey, error) { + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil { + return nil, fmt.Errorf("failed to parse private key PEM") + } + + privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse PKCS8 encoded private key: %s", err.Error()) + } + + return privateKey, nil +} diff --git a/weaver/sdks/fabric/go-sdk/readme.md b/weaver/sdks/fabric/go-sdk/readme.md index 7bac8f4880..843cb54a69 100644 --- a/weaver/sdks/fabric/go-sdk/readme.md +++ b/weaver/sdks/fabric/go-sdk/readme.md @@ -21,6 +21,51 @@ Sample content for the values of these keys can be found in the file `helpers/te Also note that similar checks need to be carried out for the connection profile `yaml` file if you want to use the yaml file instead of json. Sample content for the values of these keys can be found in the file `helpers/testdata/example/peerOrganizations/org1.example.com/connection-tls.yaml`. +### Testing membership manager functions + +The [membership manager functions](./membershipmanager) are currently not covered by unit tests. As a temporary measure,below are instructions to test them in a standalone package as follows. +- Copy [membershipmanager/membership_manager.go](./membershipmanager/membership_manager.go) to an empty folder and rename the package to `main`. +- Add a `main` function with the following contents. These are samples, and you can tweak these as needed in your setup. + ```go + func main() { + walletPath := "" // e.g., /weaver/samples/fabric/fabric-cli/src/wallet-network1/ + userName := "networkadmin" // e.g., networkadmin + connectionProfilePath := "" // e.g., /weaver/tests/network-setups/fabric/shared/network1/peerOrganizations/org1.network1.com/connection-org1.docker.json" + + fmt.Printf("Get Recorded local Membership: ") + member, _ := GetMembershipUnit(walletPath, userName, connectionProfilePath, "mychannel", "Org1MSP") + fmt.Printf("%+v\n", member) + + fmt.Printf("Get MSP Configuration for Org2MSP: ") + membership, _ := GetMSPConfigurations(walletPath, userName, connectionProfilePath, "mychannel", []string{"Org1MSP", "Org2MSP"}) + fmt.Printf("%+v\n", membership) + + fmt.Printf("Get All MSP Configuration for Org1MSP: ") + membership, _ = GetAllMSPConfigurations(walletPath, userName, connectionProfilePath, "mychannel", []string{"Org1MSP"}) + fmt.Printf("%+v\n", membership) + + fmt.Printf("Create Local Membership: ") + err := CreateLocalMembership(walletPath, userName, connectionProfilePath, "mychannel", "interop", []string{"Org1MSP"}) + fmt.Println(err) + + fmt.Printf("Update Local Membership: ") + err = UpdateLocalMembership(walletPath, userName, connectionProfilePath, "mychannel", "interop", []string{"Org1MSP"}) + fmt.Println(err) + + fmt.Printf("Read Local Membership: ") + m, err := ReadMembership(walletPath, userName, connectionProfilePath, "mychannel", "interop", "local-security-domain", []string{"Org1MSP"}) + fmt.Println(m) + fmt.Println(err) + + fmt.Printf("Delete Local Membership: ") + err = DeleteLocalMembership(walletPath, userName, connectionProfilePath, "mychannel", "interop", []string{"Org1MSP"}) + fmt.Println(err) + } + ``` +- Build the program. +- Start the Weaver testnet, e.g., by navigating to `/weaver/tests/network-setups/fabric/dev` and running `make start-interop-local`. +- Run the above program. You should see membership contents in the output with no errors. + ## Configurations - Set the output of the below command as the value of the key `"members"."Org1MSP"."value"` in the file `data/credentials/network1/membership.json` (similarly for `network2`). diff --git a/weaver/sdks/fabric/interoperation-node-sdk/src/MembershipManager.ts b/weaver/sdks/fabric/interoperation-node-sdk/src/MembershipManager.ts index c58038ad99..b0bf218d98 100644 --- a/weaver/sdks/fabric/interoperation-node-sdk/src/MembershipManager.ts +++ b/weaver/sdks/fabric/interoperation-node-sdk/src/MembershipManager.ts @@ -22,13 +22,12 @@ import { handlePromise, promisifyAll } from './helpers' async function createLocalMembership( gateway: Gateway, memberMspIds: Array, - securityDomain: string, channelName: string, weaverCCId: string ): Promise { const network = await gateway.getNetwork(channelName) const membership = getMSPConfigurations(network, memberMspIds) - membership.setSecuritydomain(securityDomain) + membership.setSecuritydomain('') const membership64 = Buffer.from(membership.serializeBinary()).toString('base64') const contract = network.getContract(weaverCCId) return await contract.submitTransaction("CreateLocalMembership", membership64); @@ -37,13 +36,12 @@ async function createLocalMembership( async function updateLocalMembership( gateway: Gateway, memberMspIds: Array, - securityDomain: string, channelName: string, weaverCCId: string ): Promise { const network = await gateway.getNetwork(channelName) const membership = getMSPConfigurations(network, memberMspIds) - membership.setSecuritydomain(securityDomain) + membership.setSecuritydomain('') const membership64 = Buffer.from(membership.serializeBinary()).toString('base64') const contract = network.getContract(weaverCCId) return await contract.submitTransaction("UpdateLocalMembership", membership64); @@ -51,16 +49,15 @@ async function updateLocalMembership( async function deleteLocalMembership( gateway: Gateway, - securityDomain: string, channelName: string, weaverCCId: string ): Promise { const network = await gateway.getNetwork(channelName) const contract = network.getContract(weaverCCId) - return await contract.submitTransaction("DeleteLocalMembership", securityDomain); + return await contract.submitTransaction("DeleteLocalMembership"); } -async function readLocalMembership( +async function readMembership( gateway: Gateway, securityDomain: string, channelName: string, @@ -68,7 +65,7 @@ async function readLocalMembership( ): Promise { const network = await gateway.getNetwork(channelName) const contract = network.getContract(weaverCCId) - return await contract.submitTransaction("DeleteLocalMembership", securityDomain); + return await contract.submitTransaction("GetMembershipBySecurityDomain", securityDomain); } function getMembershipUnit(channel: Channel, mspId: string): membership_pb.Member { @@ -204,6 +201,7 @@ export { createLocalMembership, updateLocalMembership, deleteLocalMembership, + readMembership, getMembershipUnit, getAllMSPConfigurations, syncMembershipFromIINAgent