diff --git a/Makefile b/Makefile index 207235f..ef00aea 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ unit-test: unit-test-go unit-test-node .PHONY: unit-test-go unit-test-go: cd '$(base_dir)' && \ - go test -coverprofile='$(base_dir)/coverage.out' '$(go_dir)/...' + go test -race -coverprofile='$(base_dir)/coverage.out' '$(go_dir)/...' .PHONY: unit-test-node unit-test-node: diff --git a/pkg/chaincode/approve.go b/pkg/chaincode/approve.go deleted file mode 100644 index ada2e2b..0000000 --- a/pkg/chaincode/approve.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" - "github.com/hyperledger/fabric-gateway/pkg/client" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// Approve a chaincode package for the user's own organization. The connection may be to any Gateway peer that is a -// member of the channel. -func Approve(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, chaincodeDef *Definition) error { - err := chaincodeDef.validate() - if err != nil { - return err - } - validationParameter, err := chaincodeDef.getApplicationPolicyBytes() - if err != nil { - return err - } - approveArgs := &lifecycle.ApproveChaincodeDefinitionForMyOrgArgs{ - Name: chaincodeDef.Name, - Version: chaincodeDef.Version, - Sequence: chaincodeDef.Sequence, - EndorsementPlugin: chaincodeDef.EndorsementPlugin, - ValidationPlugin: chaincodeDef.ValidationPlugin, - ValidationParameter: validationParameter, - Collections: chaincodeDef.Collections, - InitRequired: chaincodeDef.InitRequired, - Source: newChaincodeSource(chaincodeDef.PackageID), - } - approveArgsBytes, err := proto.Marshal(approveArgs) - if err != nil { - return err - } - - gw, err := gateway.New(connection, id) - if err != nil { - return err - } - defer gw.Close() - - _, err = gw.GetNetwork(chaincodeDef.ChannelName). - GetContract(lifecycleChaincodeName). - SubmitWithContext( - ctx, - approveTransactionName, - client.WithBytesArguments(approveArgsBytes), - client.WithEndorsingOrganizations(id.MspID()), - ) - if err != nil { - return fmt.Errorf("failed to approve chaincode: %w", err) - } - - return nil -} - -func newChaincodeSource(packageID string) *lifecycle.ChaincodeSource { - switch packageID { - case "": - return &lifecycle.ChaincodeSource{ - Type: &lifecycle.ChaincodeSource_Unavailable_{ - Unavailable: &lifecycle.ChaincodeSource_Unavailable{}, - }, - } - default: - return &lifecycle.ChaincodeSource{ - Type: &lifecycle.ChaincodeSource_LocalPackage{ - LocalPackage: &lifecycle.ChaincodeSource_Local{ - PackageId: packageID, - }, - }, - } - } -} diff --git a/pkg/chaincode/approve_test.go b/pkg/chaincode/approve_test.go index df02dbb..448b53c 100644 --- a/pkg/chaincode/approve_test.go +++ b/pkg/chaincode/approve_test.go @@ -3,11 +3,12 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer" @@ -85,11 +86,11 @@ func AssertEqualStatus(expected error, actual error) { var _ = Describe("Approve", func() { var channelName string - var chaincodeDefinition *Definition + var chaincodeDefinition *chaincode.Definition BeforeEach(func() { channelName = "CHANNEL" - chaincodeDefinition = &Definition{ + chaincodeDefinition = &chaincode.Definition{ Name: "CHAINCODE", Version: "1.0", Sequence: 1, @@ -125,11 +126,12 @@ var _ = Describe("Approve", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - err := Approve(ctx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Approve(ctx, chaincodeDefinition) Expect(endorseCtxErr).To(BeIdenticalTo(context.Canceled), "endorse context error") Expect(submitCtxErr).To(BeIdenticalTo(context.Canceled), "submit context error") @@ -148,8 +150,9 @@ var _ = Describe("Approve", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Approve(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Approve(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -177,8 +180,9 @@ var _ = Describe("Approve", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Approve(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Approve(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -201,15 +205,16 @@ var _ = Describe("Approve", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Approve(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Approve(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) }) DescribeTable("Proposal content", - func(specCtx SpecContext, newInput func(*Definition) *Definition, newExpected func(*lifecycle.ApproveChaincodeDefinitionForMyOrgArgs) *lifecycle.ApproveChaincodeDefinitionForMyOrgArgs) { + func(specCtx SpecContext, newInput func(*chaincode.Definition) *chaincode.Definition, newExpected func(*lifecycle.ApproveChaincodeDefinitionForMyOrgArgs) *lifecycle.ApproveChaincodeDefinitionForMyOrgArgs) { input := newInput(chaincodeDefinition) expected := newExpected(&lifecycle.ApproveChaincodeDefinitionForMyOrgArgs{ Name: chaincodeDefinition.Name, @@ -242,8 +247,9 @@ var _ = Describe("Approve", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Approve(specCtx, mockConnection, mockSigner, input) + err := gateway.Approve(specCtx, input) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(endorseRequest.GetProposedTransaction()) @@ -257,7 +263,7 @@ var _ = Describe("Approve", func() { }, Entry( "Proposal includes specified package ID", - func(in *Definition) *Definition { + func(in *chaincode.Definition) *chaincode.Definition { in.PackageID = "PACKAGE_ID" return in }, @@ -272,7 +278,7 @@ var _ = Describe("Approve", func() { ), Entry( "Proposal includes unspecified chaincode source with no package ID specified", - func(in *Definition) *Definition { + func(in *chaincode.Definition) *chaincode.Definition { return in }, func(in *lifecycle.ApproveChaincodeDefinitionForMyOrgArgs) *lifecycle.ApproveChaincodeDefinitionForMyOrgArgs { diff --git a/pkg/chaincode/chaincodeCCAAS_test.go b/pkg/chaincode/chaincodeCCAAS_test.go index f810b68..26ee109 100644 --- a/pkg/chaincode/chaincodeCCAAS_test.go +++ b/pkg/chaincode/chaincodeCCAAS_test.go @@ -1,4 +1,4 @@ -package chaincode +package chaincode_test import ( "archive/tar" @@ -9,22 +9,23 @@ import ( "path/filepath" "strings" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Package", func() { It("CCaaS", func() { - dummyConnection := Connection{ + dummyConnection := chaincode.Connection{ Address: "127.0.0.1:8080", DialTimeout: "10s", TLSRequired: false, } - dummyMeta := Metadata{ + dummyMeta := chaincode.Metadata{ Type: "ccaas", Label: "basic-asset", } - err := PackageCCAAS(dummyConnection, dummyMeta, tmpDir, "chaincode.tar.gz") + err := chaincode.PackageCCAAS(dummyConnection, dummyMeta, tmpDir, "chaincode.tar.gz") Expect(err).NotTo(HaveOccurred()) // so far no plan to verify the file file, err := os.Open(tmpDir + "/chaincode.tar.gz") diff --git a/pkg/chaincode/chaincode_suite_test.go b/pkg/chaincode/chaincode_suite_test.go index 7cb92f6..cdc178f 100644 --- a/pkg/chaincode/chaincode_suite_test.go +++ b/pkg/chaincode/chaincode_suite_test.go @@ -1,4 +1,4 @@ -package chaincode +package chaincode_test import ( "testing" diff --git a/pkg/chaincode/checkcommitreadiness.go b/pkg/chaincode/checkcommitreadiness.go deleted file mode 100644 index 8396e4d..0000000 --- a/pkg/chaincode/checkcommitreadiness.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" - "github.com/hyperledger/fabric-gateway/pkg/client" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// CheckCommitReadiness for a chaincode and return all approval records. The connection may be to any Gateway peer that -// is a member of the channel. -func CheckCommitReadiness(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, chaincodeDef *Definition) (*lifecycle.CheckCommitReadinessResult, error) { - validationParameter, err := chaincodeDef.getApplicationPolicyBytes() - if err != nil { - return nil, err - } - args := &lifecycle.CheckCommitReadinessArgs{ - Name: chaincodeDef.Name, - Version: chaincodeDef.Version, - Sequence: chaincodeDef.Sequence, - EndorsementPlugin: chaincodeDef.EndorsementPlugin, - ValidationPlugin: chaincodeDef.ValidationPlugin, - ValidationParameter: validationParameter, - Collections: chaincodeDef.Collections, - InitRequired: chaincodeDef.InitRequired, - } - argsBytes, err := proto.Marshal(args) - if err != nil { - return nil, err - } - - gw, err := gateway.New(connection, id) - if err != nil { - return nil, err - } - defer gw.Close() - - r, err := gw.GetNetwork(chaincodeDef.ChannelName). - GetContract(lifecycleChaincodeName). - EvaluateWithContext( - ctx, - checkCommitReadinessTransactionName, - client.WithBytesArguments(argsBytes), - ) - if err != nil { - return nil, fmt.Errorf("failed to check commit readiness: %w", err) - } - - result := &lifecycle.CheckCommitReadinessResult{} - if err = proto.Unmarshal(r, result); err != nil { - return nil, fmt.Errorf("failed to deserialize check commit readiness result: %w", err) - } - - return result, nil -} diff --git a/pkg/chaincode/checkcommitreadiness_test.go b/pkg/chaincode/checkcommitreadiness_test.go index 64901c7..188f4c5 100644 --- a/pkg/chaincode/checkcommitreadiness_test.go +++ b/pkg/chaincode/checkcommitreadiness_test.go @@ -3,11 +3,12 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" . "github.com/onsi/ginkgo/v2" @@ -21,11 +22,11 @@ import ( var _ = Describe("CheckCommitReadiness", func() { var channelName string - var chaincodeDefinition *Definition + var chaincodeDefinition *chaincode.Definition BeforeEach(func() { channelName = "mockchannel" - chaincodeDefinition = &Definition{ + chaincodeDefinition = &chaincode.Definition{ Name: "CHAINCODE", Version: "1.0", Sequence: 1, @@ -48,11 +49,12 @@ var _ = Describe("CheckCommitReadiness", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, _ = CheckCommitReadiness(ctx, mockConnection, mockSigner, chaincodeDefinition) + _, _ = gateway.CheckCommitReadiness(ctx, chaincodeDefinition) Expect(evaluateCtxErr).To(BeIdenticalTo(context.Canceled)) }) @@ -69,8 +71,9 @@ var _ = Describe("CheckCommitReadiness", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := CheckCommitReadiness(specCtx, mockConnection, mockSigner, chaincodeDefinition) + _, err := gateway.CheckCommitReadiness(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -96,8 +99,9 @@ var _ = Describe("CheckCommitReadiness", func() { }). Times(1) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := CheckCommitReadiness(specCtx, mockConnection, mockSigner, chaincodeDefinition) + _, err := gateway.CheckCommitReadiness(specCtx, chaincodeDefinition) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(evaluateRequest.GetProposedTransaction()) diff --git a/pkg/chaincode/commit.go b/pkg/chaincode/commit.go deleted file mode 100644 index 6eb138b..0000000 --- a/pkg/chaincode/commit.go +++ /dev/null @@ -1,60 +0,0 @@ -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" - "github.com/hyperledger/fabric-gateway/pkg/client" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// Commit a chaincode definition to the channel. This requires that sufficient organizations have approved the chaincode -// definition. The connection may be to any Gateway peer that is a member of the channel. -func Commit(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, chaincodeDef *Definition) error { - err := chaincodeDef.validate() - if err != nil { - return err - } - validationParameter, err := chaincodeDef.getApplicationPolicyBytes() - if err != nil { - return err - } - commitArgs := &lifecycle.CommitChaincodeDefinitionArgs{ - Name: chaincodeDef.Name, - Version: chaincodeDef.Version, - Sequence: chaincodeDef.Sequence, - EndorsementPlugin: chaincodeDef.EndorsementPlugin, - ValidationPlugin: chaincodeDef.ValidationPlugin, - ValidationParameter: validationParameter, - Collections: chaincodeDef.Collections, - InitRequired: chaincodeDef.InitRequired, - } - commitArgsBytes, err := proto.Marshal(commitArgs) - if err != nil { - return err - } - - gw, err := gateway.New(connection, id) - if err != nil { - return err - } - defer gw.Close() - - _, err = gw.GetNetwork(chaincodeDef.ChannelName). - GetContract(lifecycleChaincodeName). - SubmitWithContext( - ctx, - commitTransactionName, - client.WithBytesArguments(commitArgsBytes), - ) - if err != nil { - return fmt.Errorf("failed to commit chaincode: %w", err) - } - - return nil -} diff --git a/pkg/chaincode/commit_test.go b/pkg/chaincode/commit_test.go index 3699f69..7d964fa 100644 --- a/pkg/chaincode/commit_test.go +++ b/pkg/chaincode/commit_test.go @@ -3,11 +3,12 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" @@ -22,11 +23,11 @@ import ( var _ = Describe("Commit", func() { var channelName string - var chaincodeDefinition *Definition + var chaincodeDefinition *chaincode.Definition BeforeEach(func() { channelName = "CHANNEL" - chaincodeDefinition = &Definition{ + chaincodeDefinition = &chaincode.Definition{ Name: "CHAINCODE", Version: "1.0", Sequence: 1, @@ -62,11 +63,12 @@ var _ = Describe("Commit", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - err := Commit(ctx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Commit(ctx, chaincodeDefinition) Expect(endorseCtxErr).To(BeIdenticalTo(context.Canceled), "endorse context error") Expect(submitCtxErr).To(BeIdenticalTo(context.Canceled), "submit context error") @@ -85,8 +87,9 @@ var _ = Describe("Commit", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Commit(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Commit(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -114,8 +117,9 @@ var _ = Describe("Commit", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Commit(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Commit(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -138,8 +142,9 @@ var _ = Describe("Commit", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Commit(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Commit(specCtx, chaincodeDefinition) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -176,8 +181,9 @@ var _ = Describe("Commit", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - err := Commit(specCtx, mockConnection, mockSigner, chaincodeDefinition) + err := gateway.Commit(specCtx, chaincodeDefinition) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(endorseRequest.GetProposedTransaction()) diff --git a/pkg/chaincode/example_test.go b/pkg/chaincode/example_test.go index 783dec1..b557851 100644 --- a/pkg/chaincode/example_test.go +++ b/pkg/chaincode/example_test.go @@ -20,8 +20,7 @@ import ( "google.golang.org/grpc/credentials" ) -const peerName = "peer.example.org" -const peerEndpoint = peerName + ":7051" +const peerEndpoint = "peer.example.org:7051" const mspID = "Org1" const chaincodePackageFile = "basic.tar.gz" @@ -34,6 +33,9 @@ func Example() { id, err := identity.NewPrivateKeySigningIdentity(mspID, readCertificate(), readPrivateKey()) panicOnError(err) + peer := chaincode.NewPeer(connection, id) + gateway := chaincode.NewGateway(connection, id) + // Context used to manage Fabric invocations. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -43,7 +45,7 @@ func Example() { panicOnError(err) // Install chaincode package. This must be performed for each peer on which the chaincode is to be installed. - _, err = chaincode.Install(ctx, connection, id, chaincodePackage) + _, err = peer.Install(ctx, chaincodePackage) panicOnError(err) // Definition of the chaincode as it should appear on the channel. @@ -56,12 +58,12 @@ func Example() { // Approve chaincode definition. This must be performed using client identities from sufficient organizations to // satisfy the approval policy. - err = chaincode.Approve(ctx, connection, id, chaincodeDefinition) + err = gateway.Approve(ctx, chaincodeDefinition) panicOnError(err) // Commit approved chaincode definition. This can be carried out by any organization once enough approvals have // been recorded. - err = chaincode.Commit(ctx, connection, id, chaincodeDefinition) + err = gateway.Commit(ctx, chaincodeDefinition) panicOnError(err) } diff --git a/pkg/chaincode/gateway.go b/pkg/chaincode/gateway.go new file mode 100644 index 0000000..168e9b5 --- /dev/null +++ b/pkg/chaincode/gateway.go @@ -0,0 +1,278 @@ +/* +Copyright IBM Corp. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode + +import ( + "context" + "fmt" + + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" + "github.com/hyperledger/fabric-gateway/pkg/client" + "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" + + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" +) + +// Gateway peer belonging to a specific organization to which you want to target requests. +type Gateway struct { + connection grpc.ClientConnInterface + id identity.SigningIdentity +} + +// NewGateway creates a new Gateway instance. +func NewGateway(connection grpc.ClientConnInterface, id identity.SigningIdentity) *Gateway { + return &Gateway{ + connection: connection, + id: id, + } +} + +// ClientIdentity used to interact with the gateway peer. +func (g *Gateway) ClientIdentity() identity.SigningIdentity { + return g.id +} + +func (g *Gateway) lifecycleInvoke( + channelName string, + invocation func(contract *client.Contract) ([]byte, error), +) ([]byte, error) { + fabricGateway, err := gateway.New(g.connection, g.id) + if err != nil { + return nil, err + } + defer fabricGateway.Close() + + contract := fabricGateway.GetNetwork(channelName).GetContract(lifecycleChaincodeName) + return invocation(contract) +} + +// Approve a chaincode package for the user's own organization. +func (g *Gateway) Approve(ctx context.Context, chaincodeDef *Definition) error { + err := chaincodeDef.validate() + if err != nil { + return err + } + validationParameter, err := chaincodeDef.getApplicationPolicyBytes() + if err != nil { + return err + } + approveArgs := &lifecycle.ApproveChaincodeDefinitionForMyOrgArgs{ + Name: chaincodeDef.Name, + Version: chaincodeDef.Version, + Sequence: chaincodeDef.Sequence, + EndorsementPlugin: chaincodeDef.EndorsementPlugin, + ValidationPlugin: chaincodeDef.ValidationPlugin, + ValidationParameter: validationParameter, + Collections: chaincodeDef.Collections, + InitRequired: chaincodeDef.InitRequired, + Source: newChaincodeSource(chaincodeDef.PackageID), + } + approveArgsBytes, err := proto.Marshal(approveArgs) + if err != nil { + return err + } + + _, err = g.lifecycleInvoke(chaincodeDef.ChannelName, func(contract *client.Contract) ([]byte, error) { + return contract.SubmitWithContext( + ctx, + approveTransactionName, + client.WithBytesArguments(approveArgsBytes), + client.WithEndorsingOrganizations(g.id.MspID()), + ) + }) + if err != nil { + return fmt.Errorf("failed to approve chaincode: %w", err) + } + + return nil +} + +func newChaincodeSource(packageID string) *lifecycle.ChaincodeSource { + switch packageID { + case "": + return &lifecycle.ChaincodeSource{ + Type: &lifecycle.ChaincodeSource_Unavailable_{ + Unavailable: &lifecycle.ChaincodeSource_Unavailable{}, + }, + } + default: + return &lifecycle.ChaincodeSource{ + Type: &lifecycle.ChaincodeSource_LocalPackage{ + LocalPackage: &lifecycle.ChaincodeSource_Local{ + PackageId: packageID, + }, + }, + } + } +} + +// CheckCommitReadiness for a chaincode and return all approval records. +func (g *Gateway) CheckCommitReadiness(ctx context.Context, chaincodeDef *Definition) (*lifecycle.CheckCommitReadinessResult, error) { + validationParameter, err := chaincodeDef.getApplicationPolicyBytes() + if err != nil { + return nil, err + } + args := &lifecycle.CheckCommitReadinessArgs{ + Name: chaincodeDef.Name, + Version: chaincodeDef.Version, + Sequence: chaincodeDef.Sequence, + EndorsementPlugin: chaincodeDef.EndorsementPlugin, + ValidationPlugin: chaincodeDef.ValidationPlugin, + ValidationParameter: validationParameter, + Collections: chaincodeDef.Collections, + InitRequired: chaincodeDef.InitRequired, + } + argsBytes, err := proto.Marshal(args) + if err != nil { + return nil, err + } + + resultBytes, err := g.lifecycleInvoke(chaincodeDef.ChannelName, func(contract *client.Contract) ([]byte, error) { + return contract.EvaluateWithContext( + ctx, + checkCommitReadinessTransactionName, + client.WithBytesArguments(argsBytes), + ) + }) + if err != nil { + return nil, fmt.Errorf("failed to check commit readiness: %w", err) + } + + result := &lifecycle.CheckCommitReadinessResult{} + if err = proto.Unmarshal(resultBytes, result); err != nil { + return nil, fmt.Errorf("failed to deserialize check commit readiness result: %w", err) + } + + return result, nil +} + +// Commit a chaincode definition to the channel. +func (g *Gateway) Commit(ctx context.Context, chaincodeDef *Definition) error { + err := chaincodeDef.validate() + if err != nil { + return err + } + validationParameter, err := chaincodeDef.getApplicationPolicyBytes() + if err != nil { + return err + } + commitArgs := &lifecycle.CommitChaincodeDefinitionArgs{ + Name: chaincodeDef.Name, + Version: chaincodeDef.Version, + Sequence: chaincodeDef.Sequence, + EndorsementPlugin: chaincodeDef.EndorsementPlugin, + ValidationPlugin: chaincodeDef.ValidationPlugin, + ValidationParameter: validationParameter, + Collections: chaincodeDef.Collections, + InitRequired: chaincodeDef.InitRequired, + } + commitArgsBytes, err := proto.Marshal(commitArgs) + if err != nil { + return err + } + + _, err = g.lifecycleInvoke(chaincodeDef.ChannelName, func(contract *client.Contract) ([]byte, error) { + return contract.SubmitWithContext( + ctx, + commitTransactionName, + client.WithBytesArguments(commitArgsBytes), + ) + }) + if err != nil { + return fmt.Errorf("failed to commit chaincode: %w", err) + } + + return nil +} + +// QueryApproved chaincode definition for the user's own organization. +func (g *Gateway) QueryApproved(ctx context.Context, channelName string, chaincodeName string, sequence int64) (*lifecycle.QueryApprovedChaincodeDefinitionResult, error) { + queryArgs := &lifecycle.QueryApprovedChaincodeDefinitionArgs{ + Name: chaincodeName, + Sequence: sequence, + } + queryArgsBytes, err := proto.Marshal(queryArgs) + if err != nil { + return nil, err + } + + resultBytes, err := g.lifecycleInvoke(channelName, func(contract *client.Contract) ([]byte, error) { + return contract.EvaluateWithContext( + ctx, + queryApprovedTransactionName, + client.WithBytesArguments(queryArgsBytes), + client.WithEndorsingOrganizations(g.id.MspID()), + ) + }) + if err != nil { + return nil, fmt.Errorf("failed to query approved chaincode: %w", err) + } + + result := &lifecycle.QueryApprovedChaincodeDefinitionResult{} + if err = proto.Unmarshal(resultBytes, result); err != nil { + return nil, fmt.Errorf("failed to deserialize query approved chaincode result: %w", err) + } + + return result, nil +} + +// QueryCommitted returns the definitions of all committed chaincode for a given channel. +func (g *Gateway) QueryCommitted(ctx context.Context, channelName string) (*lifecycle.QueryChaincodeDefinitionsResult, error) { + queryArgs := &lifecycle.QueryChaincodeDefinitionsArgs{} + queryArgsBytes, err := proto.Marshal(queryArgs) + if err != nil { + return nil, err + } + + resultBytes, err := g.lifecycleInvoke(channelName, func(contract *client.Contract) ([]byte, error) { + return contract.EvaluateWithContext( + ctx, + queryCommittedTransactionName, + client.WithBytesArguments(queryArgsBytes), + ) + }) + if err != nil { + return nil, fmt.Errorf("failed to query committed chaincodes: %w", err) + } + + result := &lifecycle.QueryChaincodeDefinitionsResult{} + if err = proto.Unmarshal(resultBytes, result); err != nil { + return nil, fmt.Errorf("failed to deserialize query committed chaincode result: %w", err) + } + + return result, nil +} + +// QueryCommittedWithName returns the definition of the named chaincode for a given channel. +func (g *Gateway) QueryCommittedWithName(ctx context.Context, channelName string, chaincodeName string) (*lifecycle.QueryChaincodeDefinitionResult, error) { + queryArgs := &lifecycle.QueryChaincodeDefinitionArgs{ + Name: chaincodeName, + } + queryArgsBytes, err := proto.Marshal(queryArgs) + if err != nil { + return nil, err + } + + resultBytes, err := g.lifecycleInvoke(channelName, func(contract *client.Contract) ([]byte, error) { + return contract.EvaluateWithContext( + ctx, + queryCommittedWithNameTransactionName, + client.WithBytesArguments(queryArgsBytes), + ) + }) + if err != nil { + return nil, fmt.Errorf("failed to query committed chaincode: %w", err) + } + + result := &lifecycle.QueryChaincodeDefinitionResult{} + if err = proto.Unmarshal(resultBytes, result); err != nil { + return nil, fmt.Errorf("failed to deserialize query committed chaincode result: %w", err) + } + + return result, nil +} diff --git a/pkg/chaincode/gateway_test.go b/pkg/chaincode/gateway_test.go new file mode 100644 index 0000000..1088f4f --- /dev/null +++ b/pkg/chaincode/gateway_test.go @@ -0,0 +1,28 @@ +/* +Copyright IBM Corp. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode_test + +import ( + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/mock/gomock" +) + +var _ = Describe("Gateway", func() { + It("ClientIdentity", func() { + controller := gomock.NewController(GinkgoT()) + defer controller.Finish() + + mockConnection := NewMockClientConnInterface(controller) + mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewPeer(mockConnection, mockSigner) + + actual := gateway.ClientIdentity() + + Expect(actual).To(BeIdenticalTo(mockSigner)) + }) +}) diff --git a/pkg/chaincode/getinstalled.go b/pkg/chaincode/getinstalled.go deleted file mode 100644 index 633f455..0000000 --- a/pkg/chaincode/getinstalled.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/proposal" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// GetInstalled chaincode package from a specific peer. The connection must be to the specific peer where the chaincode -// is installed. -func GetInstalled(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, packageID string) ([]byte, error) { - getInstalledArgs := &lifecycle.GetInstalledChaincodePackageArgs{ - PackageId: packageID, - } - getInstalledArgsBytes, err := proto.Marshal(getInstalledArgs) - if err != nil { - return nil, err - } - - proposalProto, err := proposal.NewProposal(id, lifecycleChaincodeName, queryInstalledTransactionName, proposal.WithArguments(getInstalledArgsBytes)) - if err != nil { - return nil, err - } - - signedProposal, err := proposal.NewSignedProposal(proposalProto, id) - if err != nil { - return nil, err - } - - endorser := peer.NewEndorserClient(connection) - - proposalResponse, err := endorser.ProcessProposal(ctx, signedProposal) - if err != nil { - return nil, fmt.Errorf("failed to get installed chaincode: %w", err) - } - - if err = proposal.CheckSuccessfulResponse(proposalResponse); err != nil { - return nil, err - } - - result := &lifecycle.GetInstalledChaincodePackageResult{} - if err = proto.Unmarshal(proposalResponse.GetResponse().GetPayload(), result); err != nil { - return nil, fmt.Errorf("failed to deserialize get installed chaincode result: %w", err) - } - - return result.GetChaincodeInstallPackage(), nil -} diff --git a/pkg/chaincode/getinstalled_test.go b/pkg/chaincode/getinstalled_test.go index 4d13681..f79d3b4 100644 --- a/pkg/chaincode/getinstalled_test.go +++ b/pkg/chaincode/getinstalled_test.go @@ -3,12 +3,13 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" "errors" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/msp" "github.com/hyperledger/fabric-protos-go-apiv2/peer" @@ -34,11 +35,12 @@ var _ = Describe("GetInstalled", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, err := GetInstalled(ctx, mockConnection, mockSigner, "PACKAGE_ID") + _, err := peer.GetInstalled(ctx, "PACKAGE_ID") Expect(err).To(MatchError(context.Canceled)) }) @@ -55,8 +57,9 @@ var _ = Describe("GetInstalled", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := GetInstalled(specCtx, mockConnection, mockSigner, "PACKAGE_ID") + _, err := peer.GetInstalled(specCtx, "PACKAGE_ID") Expect(err).To(MatchError(expectedErr)) }) @@ -76,8 +79,9 @@ var _ = Describe("GetInstalled", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := GetInstalled(specCtx, mockConnection, mockSigner, "PACKAGE_ID") + _, err := peer.GetInstalled(specCtx, "PACKAGE_ID") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(And( @@ -104,8 +108,9 @@ var _ = Describe("GetInstalled", func() { Times(1) mockSigner := NewMockSigner(controller, "", nil, expected) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := GetInstalled(specCtx, mockConnection, mockSigner, "PACKAGE_ID") + _, err := peer.GetInstalled(specCtx, "PACKAGE_ID") Expect(err).NotTo(HaveOccurred()) actual := signedProposal.GetSignature() @@ -132,8 +137,9 @@ var _ = Describe("GetInstalled", func() { Times(1) mockSigner := NewMockSigner(controller, expected.Mspid, expected.IdBytes, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := GetInstalled(specCtx, mockConnection, mockSigner, "PACKAGE_ID") + _, err := peer.GetInstalled(specCtx, "PACKAGE_ID") Expect(err).NotTo(HaveOccurred()) signatureHeader := AssertUnmarshalSignatureHeader(signedProposal) @@ -161,8 +167,9 @@ var _ = Describe("GetInstalled", func() { Times(1) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := GetInstalled(specCtx, mockConnection, mockSigner, expected) + _, err := peer.GetInstalled(specCtx, expected) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(signedProposal) @@ -194,8 +201,9 @@ var _ = Describe("GetInstalled", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - actual, err := GetInstalled(specCtx, mockConnection, mockSigner, "PACKAGE_ID") + actual, err := peer.GetInstalled(specCtx, "PACKAGE_ID") Expect(err).NotTo(HaveOccurred()) Expect(actual).To(Equal(expected.GetChaincodeInstallPackage())) diff --git a/pkg/chaincode/install.go b/pkg/chaincode/install.go deleted file mode 100644 index be3cc84..0000000 --- a/pkg/chaincode/install.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - "io" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/proposal" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// Install a chaincode package to specific peer. The connection must be to the specific peer where the chaincode is to -// be installed. -func Install(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, packageReader io.Reader) (*lifecycle.InstallChaincodeResult, error) { - packageBytes, err := io.ReadAll(packageReader) - if err != nil { - return nil, fmt.Errorf("failed to read chaincode package: %w", err) - } - - installArgs := &lifecycle.InstallChaincodeArgs{ - ChaincodeInstallPackage: packageBytes, - } - installArgsBytes, err := proto.Marshal(installArgs) - if err != nil { - return nil, err - } - - proposalProto, err := proposal.NewProposal(id, lifecycleChaincodeName, installTransactionName, proposal.WithArguments(installArgsBytes)) - if err != nil { - return nil, err - } - - signedProposal, err := proposal.NewSignedProposal(proposalProto, id) - if err != nil { - return nil, err - } - - endorser := peer.NewEndorserClient(connection) - - proposalResponse, err := endorser.ProcessProposal(ctx, signedProposal) - if err != nil { - return nil, fmt.Errorf("failed to install chaincode: %w", err) - } - - if err := proposal.CheckSuccessfulResponse(proposalResponse); err != nil { - return nil, err - } - - result := &lifecycle.InstallChaincodeResult{} - if err := proto.Unmarshal(proposalResponse.GetResponse().GetPayload(), result); err != nil { - return nil, fmt.Errorf("failed to deserialize install chaincode result: %w", err) - } - - return result, nil -} diff --git a/pkg/chaincode/install_test.go b/pkg/chaincode/install_test.go index 39cb2ab..800c4ca 100644 --- a/pkg/chaincode/install_test.go +++ b/pkg/chaincode/install_test.go @@ -3,7 +3,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "bytes" @@ -12,6 +12,7 @@ import ( "io" "strings" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/msp" "github.com/hyperledger/fabric-protos-go-apiv2/peer" @@ -124,11 +125,12 @@ var _ = Describe("Install", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, err := Install(ctx, mockConnection, mockSigner, packageReader) + _, err := peer.Install(ctx, packageReader) Expect(err).To(MatchError(context.Canceled)) }) @@ -145,8 +147,9 @@ var _ = Describe("Install", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := Install(specCtx, mockConnection, mockSigner, packageReader) + _, err := peer.Install(specCtx, packageReader) Expect(err).To(MatchError(expectedErr)) }) @@ -166,8 +169,9 @@ var _ = Describe("Install", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := Install(specCtx, mockConnection, mockSigner, packageReader) + _, err := peer.Install(specCtx, packageReader) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(And( @@ -194,8 +198,9 @@ var _ = Describe("Install", func() { Times(1) mockSigner := NewMockSigner(controller, "", nil, expected) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := Install(specCtx, mockConnection, mockSigner, packageReader) + _, err := peer.Install(specCtx, packageReader) Expect(err).NotTo(HaveOccurred()) actual := signedProposal.GetSignature() @@ -219,9 +224,10 @@ var _ = Describe("Install", func() { Times(1) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) packageReader = bytes.NewReader(expected) - _, err := Install(specCtx, mockConnection, mockSigner, packageReader) + _, err := peer.Install(specCtx, packageReader) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(signedProposal) @@ -255,8 +261,9 @@ var _ = Describe("Install", func() { Times(1) mockSigner := NewMockSigner(controller, expected.Mspid, expected.IdBytes, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := Install(specCtx, mockConnection, mockSigner, packageReader) + _, err := peer.Install(specCtx, packageReader) Expect(err).NotTo(HaveOccurred()) signatureHeader := AssertUnmarshalSignatureHeader(signedProposal) @@ -284,8 +291,9 @@ var _ = Describe("Install", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - actual, err := Install(specCtx, mockConnection, mockSigner, packageReader) + actual, err := peer.Install(specCtx, packageReader) Expect(err).NotTo(HaveOccurred()) AssertProtoEqual(expected, actual) diff --git a/pkg/chaincode/peer.go b/pkg/chaincode/peer.go new file mode 100644 index 0000000..80b0818 --- /dev/null +++ b/pkg/chaincode/peer.go @@ -0,0 +1,156 @@ +/* +Copyright IBM Corp. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode + +import ( + "context" + "fmt" + "io" + + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-admin-sdk/pkg/internal/proposal" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" + + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" +) + +// Peer in a Fabric network. +type Peer struct { + endorser peer.EndorserClient + id identity.SigningIdentity +} + +// NewPeer creates a new Peer instance. +func NewPeer(connection grpc.ClientConnInterface, id identity.SigningIdentity) *Peer { + return &Peer{ + endorser: peer.NewEndorserClient(connection), + id: id, + } +} + +// ClientIdentity used to interact with the peer. +func (p *Peer) ClientIdentity() identity.SigningIdentity { + return p.id +} + +func (p *Peer) newSignedProposal( + chaincodeName string, + transactionName string, + options ...proposal.Option, +) (*peer.SignedProposal, error) { + proposalProto, err := proposal.NewProposal(p.id, chaincodeName, transactionName, options...) + if err != nil { + return nil, err + } + + signedProposal, err := proposal.NewSignedProposal(proposalProto, p.id) + if err != nil { + return nil, err + } + + return signedProposal, nil +} + +// GetInstalled chaincode package from a specific peer. +func (p *Peer) GetInstalled(ctx context.Context, packageID string) ([]byte, error) { + getInstalledArgs := &lifecycle.GetInstalledChaincodePackageArgs{ + PackageId: packageID, + } + getInstalledArgsBytes, err := proto.Marshal(getInstalledArgs) + if err != nil { + return nil, err + } + + signedProposal, err := p.newSignedProposal(lifecycleChaincodeName, queryInstalledTransactionName, proposal.WithArguments(getInstalledArgsBytes)) + if err != nil { + return nil, err + } + + proposalResponse, err := p.endorser.ProcessProposal(ctx, signedProposal) + if err != nil { + return nil, fmt.Errorf("failed to get installed chaincode: %w", err) + } + + if err = proposal.CheckSuccessfulResponse(proposalResponse); err != nil { + return nil, err + } + + result := &lifecycle.GetInstalledChaincodePackageResult{} + if err = proto.Unmarshal(proposalResponse.GetResponse().GetPayload(), result); err != nil { + return nil, fmt.Errorf("failed to deserialize get installed chaincode result: %w", err) + } + + return result.GetChaincodeInstallPackage(), nil +} + +// Install a chaincode package to specific peer. +func (p *Peer) Install(ctx context.Context, packageReader io.Reader) (*lifecycle.InstallChaincodeResult, error) { + packageBytes, err := io.ReadAll(packageReader) + if err != nil { + return nil, fmt.Errorf("failed to read chaincode package: %w", err) + } + + installArgs := &lifecycle.InstallChaincodeArgs{ + ChaincodeInstallPackage: packageBytes, + } + installArgsBytes, err := proto.Marshal(installArgs) + if err != nil { + return nil, err + } + + signedProposal, err := p.newSignedProposal(lifecycleChaincodeName, installTransactionName, proposal.WithArguments(installArgsBytes)) + if err != nil { + return nil, err + } + + proposalResponse, err := p.endorser.ProcessProposal(ctx, signedProposal) + if err != nil { + return nil, fmt.Errorf("failed to install chaincode: %w", err) + } + + if err := proposal.CheckSuccessfulResponse(proposalResponse); err != nil { + return nil, err + } + + result := &lifecycle.InstallChaincodeResult{} + if err := proto.Unmarshal(proposalResponse.GetResponse().GetPayload(), result); err != nil { + return nil, fmt.Errorf("failed to deserialize install chaincode result: %w", err) + } + + return result, nil +} + +// QueryInstalled chaincode on a specific peer. +func (p *Peer) QueryInstalled(ctx context.Context) (*lifecycle.QueryInstalledChaincodesResult, error) { + queryArgs := &lifecycle.QueryInstalledChaincodesArgs{} + queryArgsBytes, err := proto.Marshal(queryArgs) + if err != nil { + return nil, err + } + + signedProposal, err := p.newSignedProposal(lifecycleChaincodeName, queryInstalledTransactionName, proposal.WithArguments(queryArgsBytes)) + if err != nil { + return nil, err + } + + proposalResponse, err := p.endorser.ProcessProposal(ctx, signedProposal) + if err != nil { + return nil, fmt.Errorf("failed to query installed chaincode: %w", err) + } + + if err = proposal.CheckSuccessfulResponse(proposalResponse); err != nil { + return nil, err + } + + result := &lifecycle.QueryInstalledChaincodesResult{} + if err = proto.Unmarshal(proposalResponse.GetResponse().GetPayload(), result); err != nil { + return nil, fmt.Errorf("failed to deserialize query installed chaincode result: %w", err) + } + + return result, nil +} diff --git a/pkg/chaincode/peer_test.go b/pkg/chaincode/peer_test.go new file mode 100644 index 0000000..e6e666f --- /dev/null +++ b/pkg/chaincode/peer_test.go @@ -0,0 +1,28 @@ +/* +Copyright IBM Corp. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode_test + +import ( + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/mock/gomock" +) + +var _ = Describe("Peer", func() { + It("ClientIdentity", func() { + controller := gomock.NewController(GinkgoT()) + defer controller.Finish() + + mockConnection := NewMockClientConnInterface(controller) + mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) + + actual := peer.ClientIdentity() + + Expect(actual).To(BeIdenticalTo(mockSigner)) + }) +}) diff --git a/pkg/chaincode/queryapproved.go b/pkg/chaincode/queryapproved.go deleted file mode 100644 index b3dc0a0..0000000 --- a/pkg/chaincode/queryapproved.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" - "github.com/hyperledger/fabric-gateway/pkg/client" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// QueryApproved chaincode definition for the user's own organization. The connection may be to any Gateway peer that is -// a member of the channel. -func QueryApproved(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, channelID string, chaincodeName string, sequence int64) (*lifecycle.QueryApprovedChaincodeDefinitionResult, error) { - queryArgs := &lifecycle.QueryApprovedChaincodeDefinitionArgs{ - Name: chaincodeName, - Sequence: sequence, - } - queryArgsBytes, err := proto.Marshal(queryArgs) - if err != nil { - return nil, err - } - - gw, err := gateway.New(connection, id) - if err != nil { - return nil, err - } - defer gw.Close() - - resultBytes, err := gw.GetNetwork(channelID). - GetContract(lifecycleChaincodeName). - EvaluateWithContext( - ctx, - queryApprovedTransactionName, - client.WithBytesArguments(queryArgsBytes), - client.WithEndorsingOrganizations(id.MspID()), - ) - if err != nil { - return nil, fmt.Errorf("failed to query approved chaincode: %w", err) - } - - result := &lifecycle.QueryApprovedChaincodeDefinitionResult{} - if err = proto.Unmarshal(resultBytes, result); err != nil { - return nil, fmt.Errorf("failed to deserialize query approved chaincode result: %w", err) - } - - return result, nil -} diff --git a/pkg/chaincode/queryapproved_test.go b/pkg/chaincode/queryapproved_test.go index 4ba7e9d..f38305f 100644 --- a/pkg/chaincode/queryapproved_test.go +++ b/pkg/chaincode/queryapproved_test.go @@ -3,11 +3,12 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" . "github.com/onsi/ginkgo/v2" @@ -45,11 +46,12 @@ var _ = Describe("QueryApproved", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, _ = QueryApproved(ctx, mockConnection, mockSigner, channelName, chaincodeName, sequence) + _, _ = gateway.QueryApproved(ctx, channelName, chaincodeName, sequence) Expect(evaluateCtxErr).To(BeIdenticalTo(context.Canceled), "endorse context error") }) @@ -66,8 +68,9 @@ var _ = Describe("QueryApproved", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := QueryApproved(specCtx, mockConnection, mockSigner, channelName, chaincodeName, sequence) + _, err := gateway.QueryApproved(specCtx, channelName, chaincodeName, sequence) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -92,8 +95,9 @@ var _ = Describe("QueryApproved", func() { }). Times(1) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := QueryApproved(specCtx, mockConnection, mockSigner, channelName, chaincodeName, sequence) + _, err := gateway.QueryApproved(specCtx, channelName, chaincodeName, sequence) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(evaluateRequest.GetProposedTransaction()) diff --git a/pkg/chaincode/querycommitted.go b/pkg/chaincode/querycommitted.go deleted file mode 100644 index ef42e37..0000000 --- a/pkg/chaincode/querycommitted.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" - "github.com/hyperledger/fabric-gateway/pkg/client" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// QueryCommitted returns the definitions of all committed chaincode for a given channel. The connection may be to any -// Gateway peer that is a member of the channel. -func QueryCommitted(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, channelID string) (*lifecycle.QueryChaincodeDefinitionsResult, error) { - queryArgs := &lifecycle.QueryChaincodeDefinitionsArgs{} - queryArgsBytes, err := proto.Marshal(queryArgs) - if err != nil { - return nil, err - } - - gw, err := gateway.New(connection, id) - if err != nil { - return nil, err - } - defer gw.Close() - - resultBytes, err := gw.GetNetwork(channelID). - GetContract(lifecycleChaincodeName). - EvaluateWithContext( - ctx, - queryCommittedTransactionName, - client.WithBytesArguments(queryArgsBytes), - ) - if err != nil { - return nil, fmt.Errorf("failed to query committed chaincodes: %w", err) - } - - result := &lifecycle.QueryChaincodeDefinitionsResult{} - if err = proto.Unmarshal(resultBytes, result); err != nil { - return nil, fmt.Errorf("failed to deserialize query committed chaincode result: %w", err) - } - - return result, nil -} diff --git a/pkg/chaincode/querycommitted_test.go b/pkg/chaincode/querycommitted_test.go index c4a1771..52d73f5 100644 --- a/pkg/chaincode/querycommitted_test.go +++ b/pkg/chaincode/querycommitted_test.go @@ -3,11 +3,12 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" . "github.com/onsi/ginkgo/v2" @@ -41,11 +42,12 @@ var _ = Describe("QueryCommitted", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, _ = QueryCommitted(ctx, mockConnection, mockSigner, channelName) + _, _ = gateway.QueryCommitted(ctx, channelName) Expect(evaluateCtxErr).To(BeIdenticalTo(context.Canceled), "endorse context error") }) @@ -62,8 +64,9 @@ var _ = Describe("QueryCommitted", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := QueryCommitted(specCtx, mockConnection, mockSigner, channelName) + _, err := gateway.QueryCommitted(specCtx, channelName) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -85,8 +88,9 @@ var _ = Describe("QueryCommitted", func() { }). Times(1) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := QueryCommitted(specCtx, mockConnection, mockSigner, channelName) + _, err := gateway.QueryCommitted(specCtx, channelName) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(evaluateRequest.GetProposedTransaction()) diff --git a/pkg/chaincode/querycommittedwithname.go b/pkg/chaincode/querycommittedwithname.go deleted file mode 100644 index a6adcfc..0000000 --- a/pkg/chaincode/querycommittedwithname.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/gateway" - "github.com/hyperledger/fabric-gateway/pkg/client" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// QueryCommittedWithName returns the definition of the named chaincode for a given channel. The connection may be to -// any Gateway peer that is a member of the channel. -func QueryCommittedWithName(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity, channelID, chaincodeName string) (*lifecycle.QueryChaincodeDefinitionResult, error) { - queryArgs := &lifecycle.QueryChaincodeDefinitionArgs{ - Name: chaincodeName, - } - queryArgsBytes, err := proto.Marshal(queryArgs) - if err != nil { - return nil, err - } - - gw, err := gateway.New(connection, id) - if err != nil { - return nil, err - } - defer gw.Close() - - resultBytes, err := gw.GetNetwork(channelID). - GetContract(lifecycleChaincodeName). - EvaluateWithContext( - ctx, - queryCommittedWithNameTransactionName, - client.WithBytesArguments(queryArgsBytes), - ) - if err != nil { - return nil, fmt.Errorf("failed to query committed chaincode: %w", err) - } - - result := &lifecycle.QueryChaincodeDefinitionResult{} - if err = proto.Unmarshal(resultBytes, result); err != nil { - return nil, fmt.Errorf("failed to deserialize query committed chaincode result: %w", err) - } - - return result, nil -} diff --git a/pkg/chaincode/querycommittedwithname_test.go b/pkg/chaincode/querycommittedwithname_test.go index 4a3696d..9637992 100644 --- a/pkg/chaincode/querycommittedwithname_test.go +++ b/pkg/chaincode/querycommittedwithname_test.go @@ -3,11 +3,12 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" . "github.com/onsi/ginkgo/v2" @@ -43,11 +44,12 @@ var _ = Describe("QueryCommittedWithName", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, _ = QueryCommittedWithName(ctx, mockConnection, mockSigner, channelName, chaincodeName) + _, _ = gateway.QueryCommittedWithName(ctx, channelName, chaincodeName) Expect(evaluateCtxErr).To(BeIdenticalTo(context.Canceled)) }) @@ -64,8 +66,9 @@ var _ = Describe("QueryCommittedWithName", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := QueryCommittedWithName(specCtx, mockConnection, mockSigner, channelName, chaincodeName) + _, err := gateway.QueryCommittedWithName(specCtx, channelName, chaincodeName) Expect(err).To(MatchError(expectedErr)) AssertEqualStatus(expectedErr, err) @@ -89,8 +92,9 @@ var _ = Describe("QueryCommittedWithName", func() { }). Times(1) mockSigner := NewMockSigner(controller, "", nil, nil) + gateway := chaincode.NewGateway(mockConnection, mockSigner) - _, err := QueryCommittedWithName(specCtx, mockConnection, mockSigner, channelName, chaincodeName) + _, err := gateway.QueryCommittedWithName(specCtx, channelName, chaincodeName) Expect(err).NotTo(HaveOccurred()) invocationSpec := AssertUnmarshalInvocationSpec(evaluateRequest.GetProposedTransaction()) diff --git a/pkg/chaincode/queryinstalled.go b/pkg/chaincode/queryinstalled.go deleted file mode 100644 index 0edb89b..0000000 --- a/pkg/chaincode/queryinstalled.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ - -package chaincode - -import ( - "context" - "fmt" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-admin-sdk/pkg/internal/proposal" - - "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "github.com/hyperledger/fabric-protos-go-apiv2/peer/lifecycle" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -// QueryInstalled chaincode on a specific peer. The connection must be to the specific peer where the chaincode is -// installed. -func QueryInstalled(ctx context.Context, connection grpc.ClientConnInterface, id identity.SigningIdentity) (*lifecycle.QueryInstalledChaincodesResult, error) { - queryArgs := &lifecycle.QueryInstalledChaincodesArgs{} - queryArgsBytes, err := proto.Marshal(queryArgs) - if err != nil { - return nil, err - } - - proposalProto, err := proposal.NewProposal(id, lifecycleChaincodeName, queryInstalledTransactionName, proposal.WithArguments(queryArgsBytes)) - if err != nil { - return nil, err - } - - signedProposal, err := proposal.NewSignedProposal(proposalProto, id) - if err != nil { - return nil, err - } - - endorser := peer.NewEndorserClient(connection) - - proposalResponse, err := endorser.ProcessProposal(ctx, signedProposal) - if err != nil { - return nil, fmt.Errorf("failed to query installed chaincode: %w", err) - } - - if err = proposal.CheckSuccessfulResponse(proposalResponse); err != nil { - return nil, err - } - - result := &lifecycle.QueryInstalledChaincodesResult{} - if err = proto.Unmarshal(proposalResponse.GetResponse().GetPayload(), result); err != nil { - return nil, fmt.Errorf("failed to deserialize query installed chaincode result: %w", err) - } - - return result, nil -} diff --git a/pkg/chaincode/queryinstalled_test.go b/pkg/chaincode/queryinstalled_test.go index b795419..553d2ee 100644 --- a/pkg/chaincode/queryinstalled_test.go +++ b/pkg/chaincode/queryinstalled_test.go @@ -3,12 +3,13 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package chaincode +package chaincode_test import ( "context" "errors" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/msp" "github.com/hyperledger/fabric-protos-go-apiv2/peer" @@ -45,11 +46,12 @@ var _ = Describe("QueryInstalled", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) ctx, cancel := context.WithCancel(specCtx) cancel() - _, err := QueryInstalled(ctx, mockConnection, mockSigner) + _, err := peer.QueryInstalled(ctx) Expect(err).To(MatchError(context.Canceled)) }) @@ -66,8 +68,9 @@ var _ = Describe("QueryInstalled", func() { Return(expectedErr) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := QueryInstalled(specCtx, mockConnection, mockSigner) + _, err := peer.QueryInstalled(specCtx) Expect(err).To(MatchError(expectedErr)) }) @@ -87,8 +90,9 @@ var _ = Describe("QueryInstalled", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := QueryInstalled(specCtx, mockConnection, mockSigner) + _, err := peer.QueryInstalled(specCtx) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(And( @@ -115,8 +119,9 @@ var _ = Describe("QueryInstalled", func() { Times(1) mockSigner := NewMockSigner(controller, "", nil, expected) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := QueryInstalled(specCtx, mockConnection, mockSigner) + _, err := peer.QueryInstalled(specCtx) Expect(err).NotTo(HaveOccurred()) actual := signedProposal.GetSignature() @@ -143,8 +148,9 @@ var _ = Describe("QueryInstalled", func() { Times(1) mockSigner := NewMockSigner(controller, expected.Mspid, expected.IdBytes, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - _, err := QueryInstalled(specCtx, mockConnection, mockSigner) + _, err := peer.QueryInstalled(specCtx) Expect(err).NotTo(HaveOccurred()) signatureHeader := AssertUnmarshalSignatureHeader(signedProposal) @@ -178,8 +184,9 @@ var _ = Describe("QueryInstalled", func() { }) mockSigner := NewMockSigner(controller, "", nil, nil) + peer := chaincode.NewPeer(mockConnection, mockSigner) - actual, err := QueryInstalled(specCtx, mockConnection, mockSigner) + actual, err := peer.QueryInstalled(specCtx) Expect(err).NotTo(HaveOccurred()) AssertProtoEqual(expected, actual) diff --git a/pkg/chaincode/signaturepolicy_test.go b/pkg/chaincode/signaturepolicy_test.go index 0c8c606..e6a0859 100644 --- a/pkg/chaincode/signaturepolicy_test.go +++ b/pkg/chaincode/signaturepolicy_test.go @@ -1,8 +1,9 @@ -package chaincode +package chaincode_test import ( "fmt" + "github.com/hyperledger/fabric-admin-sdk/pkg/chaincode" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -10,12 +11,12 @@ import ( var _ = DescribeTable("signaturepolicyenvelope to string", func(expression string) { //gen a SignaturePolicyEnvelope from expression - applicationPolicy, err := NewApplicationPolicy(expression, "") + applicationPolicy, err := chaincode.NewApplicationPolicy(expression, "") Expect(err).NotTo(HaveOccurred()) policy := applicationPolicy.GetSignaturePolicy() //parse the SignaturePolicyEnvelope back to expression - dstExpression, err := SignaturePolicyEnvelopeToString(policy) + dstExpression, err := chaincode.SignaturePolicyEnvelopeToString(policy) Expect(err).NotTo(HaveOccurred()) fmt.Println("src Expression:", expression) diff --git a/pkg/discovery/discovery.go b/pkg/discovery/discovery.go deleted file mode 100644 index 64816d7..0000000 --- a/pkg/discovery/discovery.go +++ /dev/null @@ -1,70 +0,0 @@ -package discovery - -import ( - "context" - - "github.com/hyperledger/fabric-admin-sdk/pkg/identity" - "github.com/hyperledger/fabric-protos-go-apiv2/discovery" - "github.com/hyperledger/fabric-protos-go-apiv2/msp" - "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "google.golang.org/grpc" - "google.golang.org/protobuf/proto" -) - -func PeerMembershipQuery(ctx context.Context, conn *grpc.ClientConn, signer identity.SigningIdentity, channel string, filter *peer.ChaincodeInterest) (*discovery.PeerMembershipResult, error) { - id := &msp.SerializedIdentity{ - Mspid: signer.MspID(), - IdBytes: signer.Credentials(), - } - - idBytes, err := proto.Marshal(id) - if err != nil { - return nil, err - } - - querys := []*discovery.Query{ - { - Channel: channel, - Query: &discovery.Query_PeerQuery{ - PeerQuery: &discovery.PeerMembershipQuery{ - Filter: filter, - }, - }, - }, - } - - request := &discovery.Request{ - Authentication: &discovery.AuthInfo{ - ClientIdentity: idBytes, - ClientTlsCertHash: signer.Credentials(), - }, - Queries: querys, - } - - payload, err := proto.Marshal(request) - if err != nil { - return nil, err - } - - sig, err := signer.Sign(payload) - if err != nil { - return nil, err - } - - signedRequest := discovery.SignedRequest{ - Payload: payload, - Signature: sig, - } - - cli := discovery.NewDiscoveryClient(conn) - - rs, err := cli.Discover(ctx, &signedRequest) - if err != nil { - return nil, err - } - - for _, qrs := range rs.Results { - return qrs.GetMembers(), nil - } - return nil, nil -} diff --git a/pkg/discovery/peer.go b/pkg/discovery/peer.go new file mode 100644 index 0000000..d1a0548 --- /dev/null +++ b/pkg/discovery/peer.go @@ -0,0 +1,100 @@ +package discovery + +import ( + "context" + + "github.com/hyperledger/fabric-admin-sdk/pkg/identity" + "github.com/hyperledger/fabric-protos-go-apiv2/discovery" + "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" +) + +type Peer struct { + client discovery.DiscoveryClient + id identity.SigningIdentity + tlsCertHash []byte +} + +func NewPeer(connection grpc.ClientConnInterface, id identity.SigningIdentity, options ...PeerOption) *Peer { + result := &Peer{ + client: discovery.NewDiscoveryClient(connection), + id: id, + } + + for _, option := range options { + option(result) + } + + return result +} + +// PeerOption implements an option for creating a new Peer. +type PeerOption func(*Peer) + +// WithTLSClientCertificateHash specifies the SHA-256 hash of the TLS client certificate. This option is required only +// if mutual TLS authentication is used for the gRPC connection to the peer. +func WithTLSClientCertificateHash(certificateHash []byte) PeerOption { + return func(p *Peer) { + p.tlsCertHash = certificateHash + } +} + +// PeerMembershipQuery returns information on peers that belong to the specified channel. If no filtering of results +// is required, nil can be supplied as the filter argument. +func (p *Peer) PeerMembershipQuery(ctx context.Context, channel string, filter *peer.ChaincodeInterest) (*discovery.PeerMembershipResult, error) { + serializedID := &msp.SerializedIdentity{ + Mspid: p.id.MspID(), + IdBytes: p.id.Credentials(), + } + + idBytes, err := proto.Marshal(serializedID) + if err != nil { + return nil, err + } + + querys := []*discovery.Query{ + { + Channel: channel, + Query: &discovery.Query_PeerQuery{ + PeerQuery: &discovery.PeerMembershipQuery{ + Filter: filter, + }, + }, + }, + } + + request := &discovery.Request{ + Authentication: &discovery.AuthInfo{ + ClientIdentity: idBytes, + ClientTlsCertHash: p.tlsCertHash, + }, + Queries: querys, + } + + payload, err := proto.Marshal(request) + if err != nil { + return nil, err + } + + sig, err := p.id.Sign(payload) + if err != nil { + return nil, err + } + + signedRequest := discovery.SignedRequest{ + Payload: payload, + Signature: sig, + } + + rs, err := p.client.Discover(ctx, &signedRequest) + if err != nil { + return nil, err + } + + for _, qrs := range rs.Results { + return qrs.GetMembers(), nil + } + return nil, nil +} diff --git a/test/e2e_test.go b/test/e2e_test.go index 573dab7..acac286 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -25,7 +25,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "google.golang.org/grpc" "google.golang.org/grpc/status" ) @@ -37,11 +36,6 @@ const ( org2MspID = "Org2MSP" ) -type ConnectionDetails struct { - id identity.SigningIdentity - connection grpc.ClientConnInterface -} - func runParallel[T any](args []T, f func(T)) { var wg sync.WaitGroup for _, arg := range args { @@ -265,22 +259,20 @@ var _ = Describe("e2e", func() { Expect(err).NotTo(HaveOccurred(), "get chaincode package ID") fmt.Println(packageID) - peerConnections := []*ConnectionDetails{ - { - connection: peer1Connection, - id: org1MSP, - }, - { - connection: peer2Connection, - id: org2MSP, - }, + networkPeers := []*chaincode.Peer{ + chaincode.NewPeer(peer1Connection, org1MSP), + chaincode.NewPeer(peer2Connection, org2MSP), } + org1Gateway := chaincode.NewGateway(peer1Connection, org1MSP) + org2Gateway := chaincode.NewGateway(peer2Connection, org2MSP) + allOrgGateways := []*chaincode.Gateway{org1Gateway, org2Gateway} + // Install chaincode on each peer - runParallel(peerConnections, func(target *ConnectionDetails) { + runParallel(networkPeers, func(peer *chaincode.Peer) { ctx, cancel := context.WithTimeout(specCtx, 2*time.Minute) defer cancel() - result, err := chaincode.Install(ctx, target.connection, target.id, bytes.NewReader(chaincodePackage)) + result, err := peer.Install(ctx, bytes.NewReader(chaincodePackage)) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "chaincode install") Expect(result.GetPackageId()).To(Equal(packageID), "install chaincode package ID") @@ -288,10 +280,10 @@ var _ = Describe("e2e", func() { }) // Query installed chaincode on each peer - runParallel(peerConnections, func(target *ConnectionDetails) { + runParallel(networkPeers, func(peer *chaincode.Peer) { ctx, cancel := context.WithTimeout(specCtx, 30*time.Second) defer cancel() - result, err := chaincode.QueryInstalled(ctx, target.connection, target.id) + result, err := peer.QueryInstalled(ctx) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "query installed chaincode") installedChaincodes := result.GetInstalledChaincodes() @@ -301,10 +293,10 @@ var _ = Describe("e2e", func() { }) // Get installed chaincode package from each peer - runParallel(peerConnections, func(target *ConnectionDetails) { + runParallel(networkPeers, func(peer *chaincode.Peer) { ctx, cancel := context.WithTimeout(specCtx, 30*time.Second) defer cancel() - result, err := chaincode.GetInstalled(ctx, target.connection, target.id, packageID) + result, err := peer.GetInstalled(ctx, packageID) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "get installed chaincode package") Expect(result).NotTo(BeEmpty()) @@ -328,28 +320,28 @@ var _ = Describe("e2e", func() { } Expect(err).NotTo(HaveOccurred()) // Approve chaincode for each org - runParallel(peerConnections, func(target *ConnectionDetails) { + runParallel(allOrgGateways, func(gateway *chaincode.Gateway) { ctx, cancel := context.WithTimeout(specCtx, 30*time.Second) defer cancel() - err := chaincode.Approve(ctx, target.connection, target.id, chaincodeDef) + err := gateway.Approve(ctx, chaincodeDef) printGrpcError(err) - Expect(err).NotTo(HaveOccurred(), "approve chaincode for org %s", target.id.MspID()) + Expect(err).NotTo(HaveOccurred(), "approve chaincode for org %s", gateway.ClientIdentity().MspID()) }) // Query approved chaincode for each org - runParallel(peerConnections, func(target *ConnectionDetails) { + runParallel(allOrgGateways, func(gateway *chaincode.Gateway) { ctx, cancel := context.WithTimeout(specCtx, 30*time.Second) defer cancel() - result, err := chaincode.QueryApproved(ctx, target.connection, target.id, channelName, chaincodeDef.Name, chaincodeDef.Sequence) + result, err := gateway.QueryApproved(ctx, channelName, chaincodeDef.Name, chaincodeDef.Sequence) printGrpcError(err) - Expect(err).NotTo(HaveOccurred(), "query approved chaincode for org %s", target.id.MspID()) + Expect(err).NotTo(HaveOccurred(), "query approved chaincode for org %s", gateway.ClientIdentity().MspID()) Expect(result.GetVersion()).To(Equal(chaincodeDef.Version)) }) // Check chaincode commit readiness readinessCtx, readinessCancel := context.WithTimeout(specCtx, 30*time.Second) defer readinessCancel() - readinessResult, err := chaincode.CheckCommitReadiness(readinessCtx, peer1Connection, org1MSP, chaincodeDef) + readinessResult, err := org1Gateway.CheckCommitReadiness(readinessCtx, chaincodeDef) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "check commit readiness") Expect(readinessResult.GetApprovals()[org1MspID]).To(BeTrue()) @@ -360,14 +352,14 @@ var _ = Describe("e2e", func() { // Commit chaincode commitCtx, commitCancel := context.WithTimeout(specCtx, 30*time.Second) defer commitCancel() - err = chaincode.Commit(commitCtx, peer1Connection, org1MSP, chaincodeDef) + err = org1Gateway.Commit(commitCtx, chaincodeDef) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "commit chaincode") // Query all committed chaincode committedCtx, committedCancel := context.WithTimeout(specCtx, 30*time.Second) defer committedCancel() - committedResult, err := chaincode.QueryCommitted(committedCtx, peer1Connection, org1MSP, channelName) + committedResult, err := org1Gateway.QueryCommitted(committedCtx, channelName) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "query all committed chaincodes") committedChaincodes := committedResult.GetChaincodeDefinitions() @@ -378,7 +370,7 @@ var _ = Describe("e2e", func() { // Query named committed chaincode committedWithNameCtx, committedWithNameCancel := context.WithTimeout(specCtx, 30*time.Second) defer committedWithNameCancel() - committedWithNameResult, err := chaincode.QueryCommittedWithName(committedWithNameCtx, peer1Connection, org1MSP, channelName, chaincodeDef.Name) + committedWithNameResult, err := org1Gateway.QueryCommittedWithName(committedWithNameCtx, channelName, chaincodeDef.Name) printGrpcError(err) Expect(err).NotTo(HaveOccurred(), "query committed chaincode with name") Expect(readinessResult.GetApprovals()[org1MspID]).To(BeTrue()) @@ -390,11 +382,13 @@ var _ = Describe("e2e", func() { Expect(err).NotTo(HaveOccurred()) // check discovery as query peer membership - queryPeerMembershipctx, cancel := context.WithTimeout(specCtx, 30*time.Second) + discoveryPeer := discovery.NewPeer(peer1Connection, org1MSP) + peerMembershipCtx, cancel := context.WithTimeout(specCtx, 30*time.Second) defer cancel() - peerMembershipResult, err := discovery.PeerMembershipQuery(queryPeerMembershipctx, peer1Connection, org1MSP, channelName, nil) + peerMembershipResult, err := discoveryPeer.PeerMembershipQuery(peerMembershipCtx, channelName, nil) Expect(err).NotTo(HaveOccurred()) - Expect(peerMembershipResult).ShouldNot(BeNil()) + Expect(peerMembershipResult.GetPeersByOrg()[org1MspID].GetPeers()).To(HaveLen(1)) + Expect(peerMembershipResult.GetPeersByOrg()[org2MspID].GetPeers()).To(HaveLen(1)) }) }) })