366 lines
10 KiB
Go
366 lines
10 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package chaincode_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"time"
|
|
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
"github.com/hyperledger/fabric/bccsp/sw"
|
|
"github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode"
|
|
"github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode/mock"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"google.golang.org/grpc"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Commit", func() {
|
|
Describe("Committer", func() {
|
|
var (
|
|
mockProposalResponse *pb.ProposalResponse
|
|
mockEndorserClient *mock.EndorserClient
|
|
mockEndorserClients []chaincode.EndorserClient
|
|
mockDeliverClient *mock.PeerDeliverClient
|
|
mockSigner *mock.Signer
|
|
certificate tls.Certificate
|
|
mockBroadcastClient *mock.BroadcastClient
|
|
input *chaincode.CommitInput
|
|
committer *chaincode.Committer
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
mockEndorserClient = &mock.EndorserClient{}
|
|
|
|
mockProposalResponse = &pb.ProposalResponse{
|
|
Response: &pb.Response{
|
|
Status: 200,
|
|
},
|
|
Endorsement: &pb.Endorsement{},
|
|
}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
mockEndorserClients = []chaincode.EndorserClient{mockEndorserClient}
|
|
|
|
mockDeliverClient = &mock.PeerDeliverClient{}
|
|
input = &chaincode.CommitInput{
|
|
ChannelID: "testchannel",
|
|
Name: "testcc",
|
|
Version: "1.0",
|
|
Sequence: 1,
|
|
}
|
|
|
|
mockSigner = &mock.Signer{}
|
|
|
|
certificate = tls.Certificate{}
|
|
mockBroadcastClient = &mock.BroadcastClient{}
|
|
|
|
committer = &chaincode.Committer{
|
|
Certificate: certificate,
|
|
BroadcastClient: mockBroadcastClient,
|
|
DeliverClients: []pb.DeliverClient{mockDeliverClient},
|
|
EndorserClients: mockEndorserClients,
|
|
Input: input,
|
|
Signer: mockSigner,
|
|
}
|
|
})
|
|
|
|
It("commits a chaincode definition", func() {
|
|
err := committer.Commit()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
Context("when the channel name is not provided", func() {
|
|
BeforeEach(func() {
|
|
committer.Input.ChannelID = ""
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("The required parameter 'channelID' is empty. Rerun the command with -C flag"))
|
|
})
|
|
})
|
|
|
|
Context("when the chaincode name is not provided", func() {
|
|
BeforeEach(func() {
|
|
committer.Input.Name = ""
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("The required parameter 'name' is empty. Rerun the command with -n flag"))
|
|
})
|
|
})
|
|
|
|
Context("when the chaincode version is not provided", func() {
|
|
BeforeEach(func() {
|
|
committer.Input.Version = ""
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("The required parameter 'version' is empty. Rerun the command with -v flag"))
|
|
})
|
|
})
|
|
|
|
Context("when the sequence is not provided", func() {
|
|
BeforeEach(func() {
|
|
committer.Input.Sequence = 0
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("The required parameter 'sequence' is empty. Rerun the command with --sequence flag"))
|
|
})
|
|
})
|
|
|
|
Context("when the signer cannot be serialized", func() {
|
|
BeforeEach(func() {
|
|
mockSigner.SerializeReturns(nil, errors.New("cafe"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("failed to create proposal: failed to serialize identity: cafe"))
|
|
})
|
|
})
|
|
|
|
Context("when the signer fails to sign the proposal", func() {
|
|
BeforeEach(func() {
|
|
mockSigner.SignReturns(nil, errors.New("tea"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("failed to create signed proposal: tea"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser fails to endorse the proposal", func() {
|
|
BeforeEach(func() {
|
|
mockEndorserClient.ProcessProposalReturns(nil, errors.New("latte"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("failed to endorse proposal: latte"))
|
|
})
|
|
})
|
|
|
|
Context("when no endorser clients are set", func() {
|
|
BeforeEach(func() {
|
|
committer.EndorserClients = nil
|
|
})
|
|
|
|
It("doesn't receive any responses and returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("no proposal responses received"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser returns a nil proposal response", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse = nil
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("received nil proposal response"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser returns a proposal response with a nil response", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = nil
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("received proposal response with nil response"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser returns a non-success status", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = &pb.Response{
|
|
Status: 500,
|
|
Message: "capuccino",
|
|
}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("proposal failed with status: 500 - capuccino"))
|
|
})
|
|
})
|
|
|
|
Context("when the signer fails to sign the transaction", func() {
|
|
BeforeEach(func() {
|
|
mockSigner.SignReturnsOnCall(1, nil, errors.New("peaberry"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("failed to create signed transaction: peaberry"))
|
|
Expect(mockSigner.SignCallCount()).To(Equal(2))
|
|
})
|
|
})
|
|
|
|
Context("when the broadcast client fails to send the envelope", func() {
|
|
BeforeEach(func() {
|
|
mockBroadcastClient.SendReturns(errors.New("arabica"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("failed to send transaction: arabica"))
|
|
})
|
|
})
|
|
|
|
Context("when the wait for event flag is enabled and the transaction is committed", func() {
|
|
BeforeEach(func() {
|
|
input.WaitForEvent = true
|
|
input.WaitForEventTimeout = 3 * time.Second
|
|
input.TxID = "testtx"
|
|
input.PeerAddresses = []string{"commitpeer0"}
|
|
mockDeliverClient.DeliverFilteredStub = func(ctx context.Context, opts ...grpc.CallOption) (pb.Deliver_DeliverFilteredClient, error) {
|
|
mockDF := &mock.Deliver{}
|
|
resp := &pb.DeliverResponse{
|
|
Type: &pb.DeliverResponse_FilteredBlock{
|
|
FilteredBlock: createFilteredBlock(input.ChannelID, "testtx"),
|
|
},
|
|
}
|
|
mockDF.RecvReturns(resp, nil)
|
|
return mockDF, nil
|
|
}
|
|
})
|
|
|
|
It("waits for the event containing the txid", func() {
|
|
err := committer.Commit()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when the wait for event flag is enabled and the client can't connect", func() {
|
|
BeforeEach(func() {
|
|
input.WaitForEvent = true
|
|
input.WaitForEventTimeout = 3 * time.Second
|
|
input.TxID = "testtx"
|
|
input.PeerAddresses = []string{"commitpeer0"}
|
|
mockDeliverClient.DeliverFilteredReturns(nil, errors.New("robusta"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("failed to connect to deliver on all peers: error connecting to deliver filtered at commitpeer0: robusta"))
|
|
})
|
|
})
|
|
|
|
Context("when the wait for event flag is enabled and the transaction isn't returned before the timeout", func() {
|
|
BeforeEach(func() {
|
|
input.WaitForEvent = true
|
|
input.WaitForEventTimeout = 10 * time.Millisecond
|
|
input.TxID = "testtx"
|
|
input.PeerAddresses = []string{"commitpeer0"}
|
|
delayChan := make(chan struct{})
|
|
mockDeliverClient.DeliverFilteredStub = func(ctx context.Context, opts ...grpc.CallOption) (pb.Deliver_DeliverFilteredClient, error) {
|
|
mockDF := &mock.Deliver{}
|
|
mockDF.RecvStub = func() (*pb.DeliverResponse, error) {
|
|
<-delayChan
|
|
resp := &pb.DeliverResponse{
|
|
Type: &pb.DeliverResponse_FilteredBlock{
|
|
FilteredBlock: createFilteredBlock(input.ChannelID, "testtx"),
|
|
},
|
|
}
|
|
return resp, nil
|
|
}
|
|
return mockDF, nil
|
|
}
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committer.Commit()
|
|
Expect(err).To(MatchError("timed out waiting for txid on all peers"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("CommitCmd", func() {
|
|
var commitCmd *cobra.Command
|
|
|
|
BeforeEach(func() {
|
|
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
|
|
Expect(err).To(BeNil())
|
|
commitCmd = chaincode.CommitCmd(nil, cryptoProvider)
|
|
commitCmd.SilenceErrors = true
|
|
commitCmd.SilenceUsage = true
|
|
commitCmd.SetArgs([]string{
|
|
"--channelID=testchannel",
|
|
"--name=testcc",
|
|
"--version=testversion",
|
|
"--sequence=1",
|
|
"--peerAddresses=querypeer1",
|
|
"--tlsRootCertFiles=tls1",
|
|
"--signature-policy=AND ('Org1MSP.member','Org2MSP.member')",
|
|
})
|
|
})
|
|
|
|
AfterEach(func() {
|
|
chaincode.ResetFlags()
|
|
})
|
|
|
|
It("sets up the committer and attempts to commit the chaincode definition", func() {
|
|
err := commitCmd.Execute()
|
|
Expect(err).To(MatchError(ContainSubstring("failed to retrieve endorser client")))
|
|
})
|
|
|
|
Context("when the policy is invalid", func() {
|
|
BeforeEach(func() {
|
|
commitCmd.SetArgs([]string{
|
|
"--signature-policy=notapolicy",
|
|
"--channelID=testchannel",
|
|
"--name=testcc",
|
|
"--version=testversion",
|
|
"--sequence=1",
|
|
"--peerAddresses=querypeer1",
|
|
"--tlsRootCertFiles=tls1",
|
|
})
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := commitCmd.Execute()
|
|
Expect(err).To(MatchError("invalid signature policy: notapolicy"))
|
|
})
|
|
})
|
|
|
|
Context("when the collections config is invalid", func() {
|
|
BeforeEach(func() {
|
|
commitCmd.SetArgs([]string{
|
|
"--collections-config=idontexist.json",
|
|
"--channelID=testchannel",
|
|
"--name=testcc",
|
|
"--version=testversion",
|
|
"--sequence=1",
|
|
"--peerAddresses=querypeer1",
|
|
"--tlsRootCertFiles=tls1",
|
|
})
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := commitCmd.Execute()
|
|
Expect(err).To(MatchError("invalid collection configuration in file idontexist.json: could not read file 'idontexist.json': open idontexist.json: no such file or directory"))
|
|
})
|
|
})
|
|
})
|
|
})
|