github.com/suchongming/fabric@v2.1.1+incompatible/integration/ledger/marbles_test.go (about)

     1  /*
     2  Copyright IBM Corp All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package ledger
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/json"
    12  	"fmt"
    13  	"path/filepath"
    14  	"syscall"
    15  
    16  	"github.com/hyperledger/fabric/integration/nwo"
    17  	"github.com/hyperledger/fabric/integration/nwo/commands"
    18  	"github.com/hyperledger/fabric/integration/runner"
    19  	. "github.com/onsi/ginkgo"
    20  	. "github.com/onsi/gomega"
    21  	"github.com/onsi/gomega/gexec"
    22  	"github.com/tedsuo/ifrit"
    23  )
    24  
    25  var _ bool = Describe("all shim APIs for non-private data", func() {
    26  	var (
    27  		setup     *setup
    28  		helper    *marblesTestHelper
    29  		chaincode nwo.Chaincode
    30  	)
    31  
    32  	BeforeEach(func() {
    33  		setup = initThreeOrgsSetup()
    34  		helper = &marblesTestHelper{
    35  			networkHelper: &networkHelper{
    36  				Network:   setup.network,
    37  				orderer:   setup.orderer,
    38  				peers:     setup.peers,
    39  				testDir:   setup.testDir,
    40  				channelID: setup.channelID,
    41  			},
    42  		}
    43  
    44  		chaincode = nwo.Chaincode{
    45  			Name:            "marbles",
    46  			Version:         "0.0",
    47  			Path:            "github.com/hyperledger/fabric/integration/chaincode/marbles/cmdwithindexspecs",
    48  			Lang:            "golang",
    49  			PackageFile:     filepath.Join(setup.testDir, "marbles.tar.gz"),
    50  			Label:           "marbles",
    51  			SignaturePolicy: `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`,
    52  			Sequence:        "1",
    53  		}
    54  	})
    55  
    56  	AfterEach(func() {
    57  		setup.cleanup()
    58  	})
    59  
    60  	// assertMarbleAPIs invoke marble APIs to add/transfer/delete marbles and get marbles without rich queries.
    61  	// These APIs are applicable to both levelDB and CouchDB.
    62  	assertMarbleAPIs := func(ccName string, peer *nwo.Peer) {
    63  		height := helper.getLedgerHeight(peer)
    64  
    65  		By("adding six marbles, marble-0 to marble-5")
    66  		for i := 0; i <= 5; i++ {
    67  			helper.invokeMarblesChaincode(ccName, peer, "initMarble", fmt.Sprintf("marble-%d", i), "blue", "35", "tom")
    68  			helper.waitUntilEqualLedgerHeight(height + i + 1)
    69  		}
    70  
    71  		By("getting marbles by range")
    72  		expectedQueryResult := newMarbleQueryResult(1, 4, "blue", 35, "tom")
    73  		helper.assertQueryMarbles(ccName, peer, expectedQueryResult, "getMarblesByRange", "marble-1", "marble-5")
    74  
    75  		By("transferring marble-0 to jerry")
    76  		helper.invokeMarblesChaincode(ccName, peer, "transferMarble", "marble-0", "jerry")
    77  
    78  		By("verifying new owner of marble-0 after transfer")
    79  		expectedResult := newMarble("marble-0", "blue", 35, "jerry")
    80  		helper.assertMarbleExists(ccName, peer, expectedResult, "marble-0")
    81  
    82  		By("deleting marble-0")
    83  		helper.invokeMarblesChaincode(ccName, peer, "delete", "marble-0")
    84  
    85  		By("verifying deletion of marble-0")
    86  		helper.assertMarbleDoesNotExist(peer, ccName, "marble-0")
    87  
    88  		By("transferring marbles by color")
    89  		helper.invokeMarblesChaincode(ccName, peer, "transferMarblesBasedOnColor", "blue", "jerry")
    90  
    91  		By("verifying new owner after transfer by color")
    92  		for i := 1; i <= 5; i++ {
    93  			name := fmt.Sprintf("marble-%d", i)
    94  			expectedResult = newMarble(name, "blue", 35, "jerry")
    95  			helper.assertMarbleExists(ccName, peer, expectedResult, name)
    96  		}
    97  
    98  		By("getting history for marble-0")
    99  		expectedHistoryResult := []*marbleHistoryResult{
   100  			{IsDelete: "true"},
   101  			{IsDelete: "false", Value: newMarble("marble-0", "blue", 35, "jerry")},
   102  			{IsDelete: "false", Value: newMarble("marble-0", "blue", 35, "tom")},
   103  		}
   104  		helper.assertGetHistoryForMarble(ccName, peer, expectedHistoryResult, "marble-0")
   105  	}
   106  
   107  	// assertMarbleAPIs verifies marbles APIs using rich queries and pagination that are only applicable to CouchDB.
   108  	assertMarbleAPIsRichQueries := func(ccName string, peer *nwo.Peer) {
   109  		By("querying marbles by owner")
   110  		expectedQueryResult := newMarbleQueryResult(1, 5, "blue", 35, "jerry")
   111  		helper.assertQueryMarbles(ccName, peer, expectedQueryResult, "queryMarblesByOwner", "jerry")
   112  
   113  		By("quering marbles by search criteria")
   114  		helper.assertQueryMarbles(ccName, peer, expectedQueryResult, "queryMarbles", `{"selector":{"color":"blue"}}`)
   115  
   116  		By("quering marbles by range with pagination size 3, 1st call")
   117  		bookmark := ""
   118  		expectedQueryResult = newMarbleQueryResult(1, 3, "blue", 35, "jerry")
   119  		bookmark = helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult,
   120  			"getMarblesByRangeWithPagination", "marble-1", "marble-6", "3", bookmark)
   121  
   122  		By("quering marbles by range with pagination size 3, 2nd call")
   123  		expectedQueryResult = newMarbleQueryResult(4, 5, "blue", 35, "jerry")
   124  		bookmark = helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult,
   125  			"getMarblesByRangeWithPagination", "marble-1", "marble-6", "3", bookmark)
   126  
   127  		By("quering marbles by range with pagination size 3, 3rd call should return no marble")
   128  		expectedQueryResult = make([]*marbleQueryResult, 0)
   129  		helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult,
   130  			"getMarblesByRangeWithPagination", "marble-1", "marble-6", "3", bookmark)
   131  
   132  		By("quering marbles by search criteria with pagination size 10, 1st call")
   133  		bookmark = ""
   134  		expectedQueryResult = newMarbleQueryResult(1, 5, "blue", 35, "jerry")
   135  		bookmark = helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult,
   136  			"queryMarblesWithPagination", `{"selector":{"owner":"jerry"}}`, "10", bookmark)
   137  
   138  		By("quering marbles by search criteria with pagination size 10, 2nd call should return no marble")
   139  		expectedQueryResult = make([]*marbleQueryResult, 0)
   140  		helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult,
   141  			"queryMarblesWithPagination", `{"selector":{"owner":"jerry"}}`, "10", bookmark)
   142  	}
   143  
   144  	When("levelDB is used as stateDB", func() {
   145  		It("calls marbles APIs", func() {
   146  			peer := setup.network.Peer("org2", "peer0")
   147  
   148  			By("deploying new lifecycle chaincode")
   149  			nwo.EnableCapabilities(setup.network, setup.channelID, "Application", "V2_0", setup.orderer, setup.peers...)
   150  			helper.deployChaincode(chaincode)
   151  
   152  			By("verifying marbles chaincode APIs")
   153  			assertMarbleAPIs(chaincode.Name, peer)
   154  		})
   155  	})
   156  
   157  	When("CouchDB is used as stateDB", func() {
   158  		var (
   159  			couchProcess ifrit.Process
   160  		)
   161  
   162  		BeforeEach(func() {
   163  			By("stopping peers")
   164  			setup.stopPeers()
   165  
   166  			By("configuring a peer with couchdb")
   167  			// configure only one of the peers (org2, peer0) to use couchdb.
   168  			// Note that we do not support a channel with mixed DBs.
   169  			// However, for testing, it would be fine to use couchdb for one
   170  			// peer and sending all the couchdb related test queries to this peer
   171  			couchDB := &runner.CouchDB{}
   172  			couchProcess = ifrit.Invoke(couchDB)
   173  			Eventually(couchProcess.Ready(), runner.DefaultStartTimeout).Should(BeClosed())
   174  			Consistently(couchProcess.Wait()).ShouldNot(Receive())
   175  			couchAddr := couchDB.Address()
   176  			peer := setup.network.Peer("org2", "peer0")
   177  			core := setup.network.ReadPeerConfig(peer)
   178  			core.Ledger.State.StateDatabase = "CouchDB"
   179  			core.Ledger.State.CouchDBConfig.CouchDBAddress = couchAddr
   180  			setup.network.WritePeerConfig(peer, core)
   181  
   182  			By("restarting peers with couchDB")
   183  			setup.startPeers()
   184  		})
   185  
   186  		AfterEach(func() {
   187  			couchProcess.Signal(syscall.SIGTERM)
   188  			Eventually(couchProcess.Wait(), setup.network.EventuallyTimeout).Should(Receive())
   189  		})
   190  
   191  		It("calls marbles APIs", func() {
   192  			peer := setup.network.Peer("org2", "peer0")
   193  
   194  			By("deploying new lifecycle chaincode")
   195  			nwo.EnableCapabilities(setup.network, setup.channelID, "Application", "V2_0", setup.orderer, setup.peers...)
   196  			helper.deployChaincode(chaincode)
   197  
   198  			By("verifying marbles chaincode APIs")
   199  			assertMarbleAPIs(chaincode.Name, peer)
   200  
   201  			By("verifying marbles rich queries")
   202  			assertMarbleAPIsRichQueries(chaincode.Name, peer)
   203  		})
   204  	})
   205  })
   206  
   207  type chaincode struct {
   208  	nwo.Chaincode
   209  	isLegacy bool
   210  }
   211  
   212  // marble is the struct to unmarshal the response bytes returned from getMarble API
   213  type marble struct {
   214  	ObjectType string `json:"docType"` //docType is "marble"
   215  	Name       string `json:"name"`
   216  	Color      string `json:"color"`
   217  	Size       int    `json:"size"`
   218  	Owner      string `json:"owner"`
   219  }
   220  
   221  // marbleQueryResult is the struct to unmarshal the response bytes returned from marbles query APIs
   222  type marbleQueryResult struct {
   223  	Key    string  `json:"Key"`
   224  	Record *marble `json:"Record"`
   225  }
   226  
   227  type metadata struct {
   228  	RecordsCount string `json:"RecordsCount"`
   229  	Bookmark     string `json:"Bookmark"`
   230  }
   231  
   232  // marbleQueryResult is the struct to unmarshal the metadata bytes returned from marbles pagination APIs
   233  type paginationMetadata struct {
   234  	ResponseMetadata *metadata `json:"ResponseMetadata"`
   235  }
   236  
   237  // marbleHistoryResult is the struct to unmarshal the response bytes returned from marbles history API
   238  type marbleHistoryResult struct {
   239  	TxId      string  `json:"TxId"`
   240  	Value     *marble `json:"Value"`
   241  	Timestamp string  `json:"Timestamp"`
   242  	IsDelete  string  `json:"IsDelete"`
   243  }
   244  
   245  // newMarble creates a marble object for the given parameters
   246  func newMarble(name, color string, size int, owner string) *marble {
   247  	return &marble{"marble", name, color, size, owner}
   248  }
   249  
   250  // newMarbleQueryResult creates a slice of marbleQueryResult for the marbles based on startIndex and endIndex.
   251  // Both startIndex and endIndex are inclusive
   252  func newMarbleQueryResult(startIndex, endIndex int, color string, size int, owner string) []*marbleQueryResult {
   253  	expectedResult := make([]*marbleQueryResult, 0)
   254  	for i := startIndex; i <= endIndex; i++ {
   255  		name := fmt.Sprintf("marble-%d", i)
   256  		item := marbleQueryResult{Key: name, Record: newMarble(name, color, size, owner)}
   257  		expectedResult = append(expectedResult, &item)
   258  	}
   259  	return expectedResult
   260  }
   261  
   262  // marblesTestHelper implements helper methods to call marbles chaincode APIs and verify results
   263  type marblesTestHelper struct {
   264  	*networkHelper
   265  }
   266  
   267  // invokeMarblesChaincode invokes marbles APIs such as initMarble, transfer and delete.
   268  func (th *marblesTestHelper) invokeMarblesChaincode(chaincodeName string, peer *nwo.Peer, funcAndArgs ...string) {
   269  	command := commands.ChaincodeInvoke{
   270  		ChannelID: th.channelID,
   271  		Orderer:   th.OrdererAddress(th.orderer, nwo.ListenPort),
   272  		Name:      chaincodeName,
   273  		Ctor:      prepareChaincodeInvokeArgs(funcAndArgs...),
   274  		PeerAddresses: []string{
   275  			th.PeerAddress(peer, nwo.ListenPort),
   276  		},
   277  		WaitForEvent: true,
   278  	}
   279  	th.invokeChaincode(peer, command)
   280  	nwo.WaitUntilEqualLedgerHeight(th.Network, th.channelID, nwo.GetLedgerHeight(th.Network, peer, th.channelID), th.peers...)
   281  }
   282  
   283  // assertMarbleExists asserts that the marble exists and matches the expected result
   284  func (th *marblesTestHelper) assertMarbleExists(chaincodeName string, peer *nwo.Peer, expectedResult *marble, marbleName string) {
   285  	command := commands.ChaincodeQuery{
   286  		ChannelID: th.channelID,
   287  		Name:      chaincodeName,
   288  		Ctor:      fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName),
   289  	}
   290  	sess, err := th.PeerUserSession(peer, "User1", command)
   291  	Expect(err).NotTo(HaveOccurred())
   292  	Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
   293  	result := &marble{}
   294  	err = json.Unmarshal(sess.Out.Contents(), result)
   295  	Expect(err).NotTo(HaveOccurred())
   296  	Expect(result).To(Equal(expectedResult))
   297  
   298  }
   299  
   300  // assertMarbleDoesNotExist asserts that the marble does not exist
   301  func (th *marblesTestHelper) assertMarbleDoesNotExist(peer *nwo.Peer, chaincodeName, marbleName string) {
   302  	command := commands.ChaincodeQuery{
   303  		ChannelID: th.channelID,
   304  		Name:      chaincodeName,
   305  		Ctor:      fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName),
   306  	}
   307  	th.queryChaincode(peer, command, "Marble does not exist", false)
   308  }
   309  
   310  // assertQueryMarbles queries the chaincode and verifies the result based on the function and arguments,
   311  // including range queries and rich queries.
   312  func (th *marblesTestHelper) assertQueryMarbles(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) {
   313  	command := commands.ChaincodeQuery{
   314  		ChannelID: th.channelID,
   315  		Name:      chaincodeName,
   316  		Ctor:      prepareChaincodeInvokeArgs(funcAndArgs...),
   317  	}
   318  	sess, err := th.PeerUserSession(peer, "User1", command)
   319  	Expect(err).NotTo(HaveOccurred())
   320  	Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
   321  	results := make([]*marbleQueryResult, 0)
   322  	err = json.Unmarshal(sess.Out.Contents(), &results)
   323  	Expect(err).NotTo(HaveOccurred())
   324  	Expect(results).To(Equal(expectedResult))
   325  }
   326  
   327  // assertQueryMarbles queries the chaincode with pagination and verifies the result based on the function and arguments,
   328  // including range queries and rich queries.
   329  func (th *marblesTestHelper) assertQueryMarblesWithPagination(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) string {
   330  	command := commands.ChaincodeQuery{
   331  		ChannelID: th.channelID,
   332  		Name:      chaincodeName,
   333  		Ctor:      prepareChaincodeInvokeArgs(funcAndArgs...),
   334  	}
   335  	sess, err := th.PeerUserSession(peer, "User1", command)
   336  	Expect(err).NotTo(HaveOccurred())
   337  	Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
   338  
   339  	// response bytes contains 2 json arrays: [{"Key":...}][{"ResponseMetadata": ...}]
   340  	responseBytes := sess.Out.Contents()
   341  	index := bytes.LastIndex(responseBytes, []byte("]["))
   342  
   343  	// unmarshal and verify response result
   344  	results := make([]*marbleQueryResult, 0)
   345  	err = json.Unmarshal(responseBytes[:index+1], &results)
   346  	Expect(err).NotTo(HaveOccurred())
   347  	Expect(results).To(Equal(expectedResult))
   348  
   349  	// unmarshal ResponseMetadata and return bookmark to the caller for next call
   350  	respMetadata := make([]*paginationMetadata, 0)
   351  	err = json.Unmarshal(responseBytes[index+1:], &respMetadata)
   352  	Expect(err).NotTo(HaveOccurred())
   353  	Expect(respMetadata).To(HaveLen(1))
   354  
   355  	return respMetadata[0].ResponseMetadata.Bookmark
   356  }
   357  
   358  // assertGetHistoryForMarble queries the history for a specific marble and verifies the result
   359  func (th *marblesTestHelper) assertGetHistoryForMarble(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleHistoryResult, marbleName string) {
   360  	command := commands.ChaincodeQuery{
   361  		ChannelID: th.channelID,
   362  		Name:      chaincodeName,
   363  		Ctor:      fmt.Sprintf(`{"Args":["getHistoryForMarble","%s"]}`, marbleName),
   364  	}
   365  	sess, err := th.PeerUserSession(peer, "User1", command)
   366  	Expect(err).NotTo(HaveOccurred())
   367  	Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
   368  
   369  	// unmarshal bytes and verify history
   370  	results := make([]*marbleHistoryResult, 0)
   371  	err = json.Unmarshal(sess.Out.Contents(), &results)
   372  	Expect(err).NotTo(HaveOccurred())
   373  	Expect(results).To(HaveLen(len(expectedResult)))
   374  	for i, result := range results {
   375  		Expect(result.IsDelete).To(Equal(expectedResult[i].IsDelete))
   376  		if result.IsDelete == "true" {
   377  			Expect(result.Value).To(BeNil())
   378  			continue
   379  		}
   380  		Expect(result.Value).To(Equal(expectedResult[i].Value))
   381  	}
   382  }