github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/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/osdi23p228/fabric/integration/nwo" 17 "github.com/osdi23p228/fabric/integration/nwo/commands" 18 "github.com/osdi23p228/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/osdi23p228/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 // marble is the struct to unmarshal the response bytes returned from getMarble API 208 type marble struct { 209 ObjectType string `json:"docType"` //docType is "marble" 210 Name string `json:"name"` 211 Color string `json:"color"` 212 Size int `json:"size"` 213 Owner string `json:"owner"` 214 } 215 216 // marbleQueryResult is the struct to unmarshal the response bytes returned from marbles query APIs 217 type marbleQueryResult struct { 218 Key string `json:"Key"` 219 Record *marble `json:"Record"` 220 } 221 222 type metadata struct { 223 RecordsCount string `json:"RecordsCount"` 224 Bookmark string `json:"Bookmark"` 225 } 226 227 // marbleQueryResult is the struct to unmarshal the metadata bytes returned from marbles pagination APIs 228 type paginationMetadata struct { 229 ResponseMetadata *metadata `json:"ResponseMetadata"` 230 } 231 232 // marbleHistoryResult is the struct to unmarshal the response bytes returned from marbles history API 233 type marbleHistoryResult struct { 234 TxId string `json:"TxId"` 235 Value *marble `json:"Value"` 236 Timestamp string `json:"Timestamp"` 237 IsDelete string `json:"IsDelete"` 238 } 239 240 // newMarble creates a marble object for the given parameters 241 func newMarble(name, color string, size int, owner string) *marble { 242 return &marble{"marble", name, color, size, owner} 243 } 244 245 // newMarbleQueryResult creates a slice of marbleQueryResult for the marbles based on startIndex and endIndex. 246 // Both startIndex and endIndex are inclusive 247 func newMarbleQueryResult(startIndex, endIndex int, color string, size int, owner string) []*marbleQueryResult { 248 expectedResult := make([]*marbleQueryResult, 0) 249 for i := startIndex; i <= endIndex; i++ { 250 name := fmt.Sprintf("marble-%d", i) 251 item := marbleQueryResult{Key: name, Record: newMarble(name, color, size, owner)} 252 expectedResult = append(expectedResult, &item) 253 } 254 return expectedResult 255 } 256 257 // marblesTestHelper implements helper methods to call marbles chaincode APIs and verify results 258 type marblesTestHelper struct { 259 *networkHelper 260 } 261 262 // invokeMarblesChaincode invokes marbles APIs such as initMarble, transfer and delete. 263 func (th *marblesTestHelper) invokeMarblesChaincode(chaincodeName string, peer *nwo.Peer, funcAndArgs ...string) { 264 command := commands.ChaincodeInvoke{ 265 ChannelID: th.channelID, 266 Orderer: th.OrdererAddress(th.orderer, nwo.ListenPort), 267 Name: chaincodeName, 268 Ctor: prepareChaincodeInvokeArgs(funcAndArgs...), 269 PeerAddresses: []string{ 270 th.PeerAddress(peer, nwo.ListenPort), 271 }, 272 WaitForEvent: true, 273 } 274 th.invokeChaincode(peer, command) 275 nwo.WaitUntilEqualLedgerHeight(th.Network, th.channelID, nwo.GetLedgerHeight(th.Network, peer, th.channelID), th.peers...) 276 } 277 278 // assertMarbleExists asserts that the marble exists and matches the expected result 279 func (th *marblesTestHelper) assertMarbleExists(chaincodeName string, peer *nwo.Peer, expectedResult *marble, marbleName string) { 280 command := commands.ChaincodeQuery{ 281 ChannelID: th.channelID, 282 Name: chaincodeName, 283 Ctor: fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName), 284 } 285 sess, err := th.PeerUserSession(peer, "User1", command) 286 Expect(err).NotTo(HaveOccurred()) 287 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 288 result := &marble{} 289 err = json.Unmarshal(sess.Out.Contents(), result) 290 Expect(err).NotTo(HaveOccurred()) 291 Expect(result).To(Equal(expectedResult)) 292 293 } 294 295 // assertMarbleDoesNotExist asserts that the marble does not exist 296 func (th *marblesTestHelper) assertMarbleDoesNotExist(peer *nwo.Peer, chaincodeName, marbleName string) { 297 command := commands.ChaincodeQuery{ 298 ChannelID: th.channelID, 299 Name: chaincodeName, 300 Ctor: fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName), 301 } 302 th.queryChaincode(peer, command, "Marble does not exist", false) 303 } 304 305 // assertQueryMarbles queries the chaincode and verifies the result based on the function and arguments, 306 // including range queries and rich queries. 307 func (th *marblesTestHelper) assertQueryMarbles(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) { 308 command := commands.ChaincodeQuery{ 309 ChannelID: th.channelID, 310 Name: chaincodeName, 311 Ctor: prepareChaincodeInvokeArgs(funcAndArgs...), 312 } 313 sess, err := th.PeerUserSession(peer, "User1", command) 314 Expect(err).NotTo(HaveOccurred()) 315 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 316 results := make([]*marbleQueryResult, 0) 317 err = json.Unmarshal(sess.Out.Contents(), &results) 318 Expect(err).NotTo(HaveOccurred()) 319 Expect(results).To(Equal(expectedResult)) 320 } 321 322 // assertQueryMarbles queries the chaincode with pagination and verifies the result based on the function and arguments, 323 // including range queries and rich queries. 324 func (th *marblesTestHelper) assertQueryMarblesWithPagination(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) string { 325 command := commands.ChaincodeQuery{ 326 ChannelID: th.channelID, 327 Name: chaincodeName, 328 Ctor: prepareChaincodeInvokeArgs(funcAndArgs...), 329 } 330 sess, err := th.PeerUserSession(peer, "User1", command) 331 Expect(err).NotTo(HaveOccurred()) 332 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 333 334 // response bytes contains 2 json arrays: [{"Key":...}][{"ResponseMetadata": ...}] 335 responseBytes := sess.Out.Contents() 336 index := bytes.LastIndex(responseBytes, []byte("][")) 337 338 // unmarshal and verify response result 339 results := make([]*marbleQueryResult, 0) 340 err = json.Unmarshal(responseBytes[:index+1], &results) 341 Expect(err).NotTo(HaveOccurred()) 342 Expect(results).To(Equal(expectedResult)) 343 344 // unmarshal ResponseMetadata and return bookmark to the caller for next call 345 respMetadata := make([]*paginationMetadata, 0) 346 err = json.Unmarshal(responseBytes[index+1:], &respMetadata) 347 Expect(err).NotTo(HaveOccurred()) 348 Expect(respMetadata).To(HaveLen(1)) 349 350 return respMetadata[0].ResponseMetadata.Bookmark 351 } 352 353 // assertGetHistoryForMarble queries the history for a specific marble and verifies the result 354 func (th *marblesTestHelper) assertGetHistoryForMarble(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleHistoryResult, marbleName string) { 355 command := commands.ChaincodeQuery{ 356 ChannelID: th.channelID, 357 Name: chaincodeName, 358 Ctor: fmt.Sprintf(`{"Args":["getHistoryForMarble","%s"]}`, marbleName), 359 } 360 sess, err := th.PeerUserSession(peer, "User1", command) 361 Expect(err).NotTo(HaveOccurred()) 362 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 363 364 // unmarshal bytes and verify history 365 results := make([]*marbleHistoryResult, 0) 366 err = json.Unmarshal(sess.Out.Contents(), &results) 367 Expect(err).NotTo(HaveOccurred()) 368 Expect(results).To(HaveLen(len(expectedResult))) 369 for i, result := range results { 370 Expect(result.IsDelete).To(Equal(expectedResult[i].IsDelete)) 371 if result.IsDelete == "true" { 372 Expect(result.Value).To(BeNil()) 373 continue 374 } 375 Expect(result.Value).To(Equal(expectedResult[i].Value)) 376 } 377 }