go_study/fabric-main/integration/gateway/gateway_test.go

550 lines
19 KiB
Go

/*
Copyright IBM Corp All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package gateway
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"time"
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric-protos-go/gateway"
"github.com/hyperledger/fabric-protos-go/orderer"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric-protos-go/peer/lifecycle"
"github.com/hyperledger/fabric/integration/channelparticipation"
"github.com/hyperledger/fabric/integration/nwo"
"github.com/hyperledger/fabric/protoutil"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/tedsuo/ifrit"
ginkgomon "github.com/tedsuo/ifrit/ginkgomon_v2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func NewProposedTransaction(signingIdentity *nwo.SigningIdentity, channelName, chaincodeName, transactionName string, transientData map[string][]byte, args ...[]byte) (*peer.SignedProposal, string) {
proposal, transactionID := newProposalProto(signingIdentity, channelName, chaincodeName, transactionName, transientData, args...)
signedProposal, err := protoutil.GetSignedProposal(proposal, signingIdentity)
Expect(err).NotTo(HaveOccurred())
return signedProposal, transactionID
}
func newProposalProto(signingIdentity *nwo.SigningIdentity, channelName, chaincodeName, transactionName string, transientData map[string][]byte, args ...[]byte) (*peer.Proposal, string) {
creator, err := signingIdentity.Serialize()
Expect(err).NotTo(HaveOccurred())
invocationSpec := &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_NODE,
ChaincodeId: &peer.ChaincodeID{Name: chaincodeName},
Input: &peer.ChaincodeInput{Args: chaincodeArgs(transactionName, args...)},
},
}
result, transactionID, err := protoutil.CreateChaincodeProposalWithTransient(
common.HeaderType_ENDORSER_TRANSACTION,
channelName,
invocationSpec,
creator,
transientData,
)
Expect(err).NotTo(HaveOccurred())
return result, transactionID
}
func chaincodeArgs(transactionName string, args ...[]byte) [][]byte {
result := make([][]byte, len(args)+1)
result[0] = []byte(transactionName)
copy(result[1:], args)
return result
}
var _ = Describe("GatewayService basic", func() {
var (
testDir string
network *nwo.Network
org1Peer0 *nwo.Peer
ordererRunner *ginkgomon.Runner
ordererProcess, peerProcess ifrit.Process
conn *grpc.ClientConn
gatewayClient gateway.GatewayClient
ctx context.Context
cancel context.CancelFunc
signingIdentity *nwo.SigningIdentity
)
BeforeEach(func() {
var err error
testDir, err = ioutil.TempDir("", "gateway")
Expect(err).NotTo(HaveOccurred())
client, err := docker.NewClientFromEnv()
Expect(err).NotTo(HaveOccurred())
config := nwo.BasicEtcdRaft()
network = nwo.New(config, testDir, client, StartPort(), components)
network.GenerateConfigTree()
network.Bootstrap()
// Start all the fabric processes
ordererRunner, ordererProcess, peerProcess = network.StartSingleOrdererNetwork("orderer")
By("setting up the channel")
orderer := network.Orderer("orderer")
channelparticipation.JoinOrdererJoinPeersAppChannel(network, "testchannel", orderer, ordererRunner)
network.VerifyMembership(
network.PeersWithChannel("testchannel"),
"testchannel",
)
nwo.EnableCapabilities(
network,
"testchannel",
"Application", "V2_0",
orderer,
network.PeersWithChannel("testchannel")...,
)
chaincode := nwo.Chaincode{
Name: "gatewaycc",
Version: "0.0",
Path: components.Build("github.com/hyperledger/fabric/integration/chaincode/simple/cmd"),
Lang: "binary",
PackageFile: filepath.Join(testDir, "gatewaycc.tar.gz"),
Ctor: `{"Args":[]}`,
SignaturePolicy: `AND ('Org1MSP.peer')`,
Sequence: "1",
InitRequired: false,
Label: "gatewaycc_label",
}
nwo.DeployChaincode(network, "testchannel", orderer, chaincode)
org1Peer0 = network.Peer("Org1", "peer0")
conn = network.PeerClientConn(org1Peer0)
gatewayClient = gateway.NewGatewayClient(conn)
ctx, cancel = context.WithTimeout(context.Background(), network.EventuallyTimeout)
signingIdentity = network.PeerUserSigner(org1Peer0, "User1")
})
AfterEach(func() {
conn.Close()
cancel()
if ordererProcess != nil {
ordererProcess.Signal(syscall.SIGTERM)
Eventually(ordererProcess.Wait(), network.EventuallyTimeout).Should(Receive())
}
if peerProcess != nil {
peerProcess.Signal(syscall.SIGTERM)
Eventually(peerProcess.Wait(), network.EventuallyTimeout).Should(Receive())
}
if network != nil {
network.Cleanup()
}
os.RemoveAll(testDir)
})
submitTransaction := func(transactionName string, args ...[]byte) (*peer.Response, string) {
proposedTransaction, transactionID := NewProposedTransaction(signingIdentity, "testchannel", "gatewaycc", transactionName, nil, args...)
endorseRequest := &gateway.EndorseRequest{
TransactionId: transactionID,
ChannelId: "testchannel",
ProposedTransaction: proposedTransaction,
}
endorseResponse, err := gatewayClient.Endorse(ctx, endorseRequest)
Expect(err).NotTo(HaveOccurred())
preparedTransaction := endorseResponse.GetPreparedTransaction()
preparedTransaction.Signature, err = signingIdentity.Sign(preparedTransaction.Payload)
Expect(err).NotTo(HaveOccurred())
submitRequest := &gateway.SubmitRequest{
TransactionId: transactionID,
ChannelId: "testchannel",
PreparedTransaction: preparedTransaction,
}
_, err = gatewayClient.Submit(ctx, submitRequest)
Expect(err).NotTo(HaveOccurred())
chaincodeAction, err := protoutil.GetActionFromEnvelopeMsg(endorseResponse.GetPreparedTransaction())
Expect(err).NotTo(HaveOccurred())
return chaincodeAction.GetResponse(), transactionID
}
commitStatus := func(transactionID string, identity func() ([]byte, error), sign func(msg []byte) ([]byte, error)) (*gateway.CommitStatusResponse, error) {
idBytes, err := identity()
Expect(err).NotTo(HaveOccurred())
statusRequest := &gateway.CommitStatusRequest{
ChannelId: "testchannel",
Identity: idBytes,
TransactionId: transactionID,
}
statusRequestBytes, err := proto.Marshal(statusRequest)
Expect(err).NotTo(HaveOccurred())
signature, err := sign(statusRequestBytes)
Expect(err).NotTo(HaveOccurred())
signedStatusRequest := &gateway.SignedCommitStatusRequest{
Request: statusRequestBytes,
Signature: signature,
}
return gatewayClient.CommitStatus(ctx, signedStatusRequest)
}
chaincodeEvents := func(
ctx context.Context,
startPosition *orderer.SeekPosition,
afterTxID string,
identity func() ([]byte, error),
sign func(msg []byte) ([]byte, error),
) (gateway.Gateway_ChaincodeEventsClient, error) {
identityBytes, err := identity()
Expect(err).NotTo(HaveOccurred())
request := &gateway.ChaincodeEventsRequest{
ChannelId: "testchannel",
ChaincodeId: "gatewaycc",
Identity: identityBytes,
}
if startPosition != nil {
request.StartPosition = startPosition
}
if len(afterTxID) > 0 {
request.AfterTransactionId = afterTxID
}
requestBytes, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
signature, err := sign(requestBytes)
Expect(err).NotTo(HaveOccurred())
signedRequest := &gateway.SignedChaincodeEventsRequest{
Request: requestBytes,
Signature: signature,
}
return gatewayClient.ChaincodeEvents(ctx, signedRequest)
}
Describe("Evaluate", func() {
It("should respond with the expected result", func() {
proposedTransaction, transactionID := NewProposedTransaction(signingIdentity, "testchannel", "gatewaycc", "respond", nil, []byte("200"), []byte("conga message"), []byte("conga payload"))
request := &gateway.EvaluateRequest{
TransactionId: transactionID,
ChannelId: "testchannel",
ProposedTransaction: proposedTransaction,
}
response, err := gatewayClient.Evaluate(ctx, request)
Expect(err).NotTo(HaveOccurred())
expectedResponse := &gateway.EvaluateResponse{
Result: &peer.Response{
Status: 200,
Message: "conga message",
Payload: []byte("conga payload"),
},
}
Expect(response.Result.Payload).To(Equal(expectedResponse.Result.Payload))
Expect(proto.Equal(response, expectedResponse)).To(BeTrue(), "Expected\n\t%#v\nto proto.Equal\n\t%#v", response, expectedResponse)
})
It("should respond with system chaincode result", func() {
proposedTransaction, transactionID := NewProposedTransaction(signingIdentity, "testchannel", "qscc", "GetChainInfo", nil, []byte("testchannel"))
request := &gateway.EvaluateRequest{
TransactionId: transactionID,
ChannelId: "testchannel",
ProposedTransaction: proposedTransaction,
}
response, err := gatewayClient.Evaluate(ctx, request)
Expect(err).NotTo(HaveOccurred())
status := common.Status(response.GetResult().GetStatus())
Expect(status).To(Equal(common.Status_SUCCESS))
blockchainInfo := new(common.BlockchainInfo)
Expect(proto.Unmarshal(response.GetResult().GetPayload(), blockchainInfo)).NotTo(HaveOccurred())
})
})
Describe("Submit", func() {
It("should respond with the expected result", func() {
result, _ := submitTransaction("respond", []byte("200"), []byte("conga message"), []byte("conga payload"))
expectedResult := &peer.Response{
Status: 200,
Message: "conga message",
Payload: []byte("conga payload"),
}
Expect(result.Payload).To(Equal(expectedResult.Payload))
Expect(proto.Equal(result, expectedResult)).To(BeTrue(), "Expected\n\t%#v\nto proto.Equal\n\t%#v", result, expectedResult)
})
It("should endorse a system chaincode transaction", func() {
arg, err := proto.Marshal(&lifecycle.QueryInstalledChaincodesArgs{})
Expect(err).NotTo(HaveOccurred())
adminSigner := network.PeerUserSigner(org1Peer0, "Admin")
proposedTransaction, transactionID := NewProposedTransaction(adminSigner, "testchannel", "_lifecycle", "QueryInstalledChaincodes", nil, arg)
request := &gateway.EndorseRequest{
TransactionId: transactionID,
ChannelId: "testchannel",
ProposedTransaction: proposedTransaction,
EndorsingOrganizations: []string{adminSigner.MSPID}, // Only use peers for our admin ID org
}
response, err := gatewayClient.Endorse(ctx, request)
Expect(err).NotTo(HaveOccurred())
chaincodeAction, err := protoutil.GetActionFromEnvelopeMsg(response.GetPreparedTransaction())
Expect(err).NotTo(HaveOccurred())
queryResult := new(lifecycle.QueryInstalledChaincodesResult)
Expect(proto.Unmarshal(chaincodeAction.GetResponse().GetPayload(), queryResult)).NotTo(HaveOccurred())
})
})
Describe("CommitStatus", func() {
It("should respond with status of submitted transaction", func() {
_, transactionID := submitTransaction("respond", []byte("200"), []byte("conga message"), []byte("conga payload"))
statusResult, err := commitStatus(transactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
Expect(statusResult.Result).To(Equal(peer.TxValidationCode_VALID))
})
It("should respond with block number", func() {
_, transactionID := submitTransaction("respond", []byte("200"), []byte("conga message"), []byte("conga payload"))
firstStatus, err := commitStatus(transactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
_, transactionID = submitTransaction("respond", []byte("200"), []byte("conga message"), []byte("conga payload"))
nextStatus, err := commitStatus(transactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
Expect(nextStatus.BlockNumber).To(Equal(firstStatus.BlockNumber + 1))
})
It("should fail on unauthorized identity", func() {
_, transactionID := submitTransaction("respond", []byte("200"), []byte("conga message"), []byte("conga payload"))
badIdentity := network.OrdererUserSigner(network.Orderer("orderer"), "Admin")
_, err := commitStatus(transactionID, badIdentity.Serialize, signingIdentity.Sign)
Expect(err).To(HaveOccurred())
grpcErr, _ := status.FromError(err)
Expect(grpcErr.Code()).To(Equal(codes.PermissionDenied))
})
It("should fail on bad signature", func() {
_, transactionID := submitTransaction("respond", []byte("200"), []byte("conga message"), []byte("conga payload"))
badSign := func(digest []byte) ([]byte, error) {
return signingIdentity.Sign([]byte("WRONG"))
}
_, err := commitStatus(transactionID, signingIdentity.Serialize, badSign)
Expect(err).To(HaveOccurred())
grpcErr, _ := status.FromError(err)
Expect(grpcErr.Code()).To(Equal(codes.PermissionDenied))
})
})
Describe("ChaincodeEvents", func() {
It("should respond with emitted chaincode events", func() {
eventCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
startPosition := &orderer.SeekPosition{
Type: &orderer.SeekPosition_NextCommit{
NextCommit: &orderer.SeekNextCommit{},
},
}
eventsClient, err := chaincodeEvents(eventCtx, startPosition, "", signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
_, transactionID := submitTransaction("event", []byte("EVENT_NAME"), []byte("EVENT_PAYLOAD"))
event, err := eventsClient.Recv()
Expect(err).NotTo(HaveOccurred())
Expect(event.Events).To(HaveLen(1), "number of events")
expectedEvent := &peer.ChaincodeEvent{
ChaincodeId: "gatewaycc",
TxId: transactionID,
EventName: "EVENT_NAME",
Payload: []byte("EVENT_PAYLOAD"),
}
Expect(proto.Equal(event.Events[0], expectedEvent)).To(BeTrue(), "Expected\n\t%#v\nto proto.Equal\n\t%#v", event.Events[0], expectedEvent)
})
It("should respond with replayed chaincode events", func() {
_, transactionID := submitTransaction("event", []byte("EVENT_NAME"), []byte("EVENT_PAYLOAD"))
statusResult, err := commitStatus(transactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
eventCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
startPosition := &orderer.SeekPosition{
Type: &orderer.SeekPosition_Specified{
Specified: &orderer.SeekSpecified{
Number: statusResult.BlockNumber,
},
},
}
eventsClient, err := chaincodeEvents(eventCtx, startPosition, "", signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
event, err := eventsClient.Recv()
Expect(err).NotTo(HaveOccurred())
Expect(event.BlockNumber).To(Equal(statusResult.BlockNumber), "block number")
Expect(event.Events).To(HaveLen(1), "number of events")
expectedEvent := &peer.ChaincodeEvent{
ChaincodeId: "gatewaycc",
TxId: transactionID,
EventName: "EVENT_NAME",
Payload: []byte("EVENT_PAYLOAD"),
}
Expect(proto.Equal(event.Events[0], expectedEvent)).To(BeTrue(), "Expected\n\t%#v\nto proto.Equal\n\t%#v", event.Events[0], expectedEvent)
})
It("should respond with replayed chaincode events after specified transaction ID", func() {
_, afterTransactionID := submitTransaction("event", []byte("WRONG_EVENT_NAME"), []byte("WRONG_EVENT_PAYLOAD"))
_, nextTransactionID := submitTransaction("event", []byte("CORRECT_EVENT_NAME"), []byte("CORRECT_EVENT_PAYLOAD"))
statusResult, err := commitStatus(afterTransactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
_, err = commitStatus(nextTransactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
eventCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
startPosition := &orderer.SeekPosition{
Type: &orderer.SeekPosition_Specified{
Specified: &orderer.SeekSpecified{
Number: statusResult.BlockNumber,
},
},
}
eventsClient, err := chaincodeEvents(eventCtx, startPosition, afterTransactionID, signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
event, err := eventsClient.Recv()
Expect(err).NotTo(HaveOccurred())
Expect(event.Events).To(HaveLen(1), "number of events")
expectedEvent := &peer.ChaincodeEvent{
ChaincodeId: "gatewaycc",
TxId: nextTransactionID,
EventName: "CORRECT_EVENT_NAME",
Payload: []byte("CORRECT_EVENT_PAYLOAD"),
}
Expect(proto.Equal(event.Events[0], expectedEvent)).To(BeTrue(), "Expected\n\t%#v\nto proto.Equal\n\t%#v", event.Events[0], expectedEvent)
})
It("should default to next commit if start position not specified", func() {
eventCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
var startPosition *orderer.SeekPosition
eventsClient, err := chaincodeEvents(eventCtx, startPosition, "", signingIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
_, transactionID := submitTransaction("event", []byte("EVENT_NAME"), []byte("EVENT_PAYLOAD"))
event, err := eventsClient.Recv()
Expect(err).NotTo(HaveOccurred())
Expect(event.Events).To(HaveLen(1), "number of events")
expectedEvent := &peer.ChaincodeEvent{
ChaincodeId: "gatewaycc",
TxId: transactionID,
EventName: "EVENT_NAME",
Payload: []byte("EVENT_PAYLOAD"),
}
Expect(proto.Equal(event.Events[0], expectedEvent)).To(BeTrue(), "Expected\n\t%#v\nto proto.Equal\n\t%#v", event.Events[0], expectedEvent)
})
It("should fail on unauthorized identity", func() {
badIdentity := network.OrdererUserSigner(network.Orderer("orderer"), "Admin")
eventCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
startPosition := &orderer.SeekPosition{
Type: &orderer.SeekPosition_NextCommit{
NextCommit: &orderer.SeekNextCommit{},
},
}
eventsClient, err := chaincodeEvents(eventCtx, startPosition, "", badIdentity.Serialize, signingIdentity.Sign)
Expect(err).NotTo(HaveOccurred())
event, err := eventsClient.Recv()
Expect(err).To(HaveOccurred(), "expected error but got event: %v", event)
grpcErr, _ := status.FromError(err)
Expect(grpcErr.Code()).To(Equal(codes.PermissionDenied))
})
It("should fail on bad signature", func() {
badSign := func(digest []byte) ([]byte, error) {
return signingIdentity.Sign([]byte("WRONG"))
}
eventCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
startPosition := &orderer.SeekPosition{
Type: &orderer.SeekPosition_NextCommit{
NextCommit: &orderer.SeekNextCommit{},
},
}
eventsClient, err := chaincodeEvents(eventCtx, startPosition, "", signingIdentity.Serialize, badSign)
Expect(err).NotTo(HaveOccurred())
event, err := eventsClient.Recv()
Expect(err).To(HaveOccurred(), "expected error but got event: %v", event)
grpcErr, _ := status.FromError(err)
Expect(grpcErr.Code()).To(Equal(codes.PermissionDenied))
})
})
})