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 }