github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/integration/pvtdata/pvtdata_test.go (about)

     1  /*
     2  Copyright IBM Corp All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package pvtdata
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"encoding/base64"
    13  	"fmt"
    14  	"io/ioutil"
    15  	"os"
    16  	"path/filepath"
    17  	"strconv"
    18  	"syscall"
    19  	"time"
    20  
    21  	"github.com/golang/protobuf/proto"
    22  	"github.com/golang/protobuf/ptypes"
    23  	. "github.com/onsi/ginkgo"
    24  	. "github.com/onsi/gomega"
    25  	"github.com/pkg/errors"
    26  	"google.golang.org/grpc"
    27  
    28  	docker "github.com/fsouza/go-dockerclient"
    29  	cb "github.com/hyperledger/fabric-protos-go/common"
    30  	"github.com/hyperledger/fabric-protos-go/ledger/rwset"
    31  	"github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset"
    32  	ab "github.com/hyperledger/fabric-protos-go/orderer"
    33  	pb "github.com/hyperledger/fabric-protos-go/peer"
    34  	"github.com/hyperledger/fabric/bccsp/sw"
    35  	"github.com/hyperledger/fabric/common/crypto"
    36  	"github.com/hyperledger/fabric/core/comm"
    37  	"github.com/hyperledger/fabric/core/ledger/util"
    38  	"github.com/hyperledger/fabric/integration/nwo"
    39  	"github.com/hyperledger/fabric/integration/nwo/commands"
    40  	"github.com/hyperledger/fabric/msp"
    41  	"github.com/hyperledger/fabric/protoutil"
    42  	"github.com/onsi/gomega/gbytes"
    43  	"github.com/onsi/gomega/gexec"
    44  	"github.com/tedsuo/ifrit"
    45  )
    46  
    47  // The chaincode used in these tests has two collections defined:
    48  // collectionMarbles and collectionMarblePrivateDetails
    49  // when calling QueryChaincode with first arg "readMarble", it will query collectionMarbles
    50  // when calling QueryChaincode with first arg "readMarblePrivateDetails", it will query collectionMarblePrivateDetails
    51  
    52  const channelID = "testchannel"
    53  
    54  var _ bool = Describe("PrivateData", func() {
    55  	var (
    56  		network *nwo.Network
    57  		process ifrit.Process
    58  
    59  		orderer *nwo.Orderer
    60  	)
    61  
    62  	AfterEach(func() {
    63  		testCleanup(network, process)
    64  	})
    65  
    66  	Describe("Dissemination when pulling is disabled", func() {
    67  		BeforeEach(func() {
    68  			By("setting up the network")
    69  			network = initThreeOrgsSetup()
    70  
    71  			By("setting the pull retry threshold to 0 on all peers")
    72  			// set pull retry threshold to 0
    73  			for _, p := range network.Peers {
    74  				core := network.ReadPeerConfig(p)
    75  				core.Peer.Gossip.PvtData.PullRetryThreshold = 0
    76  				network.WritePeerConfig(p, core)
    77  			}
    78  
    79  			By("starting the network")
    80  			process, orderer = startNetwork(network)
    81  		})
    82  
    83  		It("disseminates private data per collections_config1", func() {
    84  			By("deploying legacy chaincode and adding marble1")
    85  			testChaincode := chaincode{
    86  				Chaincode: nwo.Chaincode{
    87  					Name:    "marblesp",
    88  					Version: "1.0",
    89  					Path:    "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd",
    90  					Ctor:    `{"Args":["init"]}`,
    91  					Policy:  `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`,
    92  					// collections_config1.json defines the access as follows:
    93  					// 1. collectionMarbles - Org1, Org2 have access to this collection
    94  					// 2. collectionMarblePrivateDetails - Org2 and Org3 have access to this collection
    95  					CollectionsConfig: collectionConfig("collections_config1.json"),
    96  				},
    97  				isLegacy: true,
    98  			}
    99  			deployChaincode(network, orderer, testChaincode)
   100  			addMarble(network, orderer, testChaincode.Name,
   101  				`{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`,
   102  				network.Peer("Org1", "peer0"),
   103  			)
   104  
   105  			assertPvtdataPresencePerCollectionConfig1(network, testChaincode.Name, "marble1")
   106  		})
   107  
   108  		When("collection config does not have maxPeerCount or requiredPeerCount", func() {
   109  			It("disseminates private data per collections_config7 with default maxPeerCount and requiredPeerCount", func() {
   110  				By("deploying legacy chaincode and adding marble1")
   111  				testChaincode := chaincode{
   112  					Chaincode: nwo.Chaincode{
   113  						Name:    "marblesp",
   114  						Version: "1.0",
   115  						Path:    "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd",
   116  						Ctor:    `{"Args":["init"]}`,
   117  						Policy:  `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`,
   118  						// collections_config1.json defines the access as follows:
   119  						// 1. collectionMarbles - Org1, Org2 have access to this collection
   120  						// 2. collectionMarblePrivateDetails - Org2 and Org3 have access to this collection
   121  						CollectionsConfig: collectionConfig("collections_config7.json"),
   122  					},
   123  					isLegacy: true,
   124  				}
   125  				deployChaincode(network, orderer, testChaincode)
   126  				peer := network.Peer("Org1", "peer0")
   127  				addMarble(network, orderer, testChaincode.Name,
   128  					`{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`,
   129  					peer,
   130  				)
   131  
   132  				assertPvtdataPresencePerCollectionConfig7(network, testChaincode.Name, "marble1", peer)
   133  			})
   134  		})
   135  	})
   136  
   137  	Describe("Pvtdata behavior with untouched peer configs", func() {
   138  		var (
   139  			legacyChaincode       nwo.Chaincode
   140  			newLifecycleChaincode nwo.Chaincode
   141  			testChaincode         chaincode
   142  
   143  			org1Peer1, org2Peer1 *nwo.Peer
   144  		)
   145  
   146  		BeforeEach(func() {
   147  			By("setting up the network")
   148  			network = initThreeOrgsSetup()
   149  			legacyChaincode = nwo.Chaincode{
   150  				Name:    "marblesp",
   151  				Version: "1.0",
   152  				Path:    "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd",
   153  				Ctor:    `{"Args":["init"]}`,
   154  				Policy:  `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`,
   155  				// collections_config1.json defines the access as follows:
   156  				// 1. collectionMarbles - Org1, Org2 have access to this collection
   157  				// 2. collectionMarblePrivateDetails - Org2 and Org3 have access to this collection
   158  				CollectionsConfig: collectionConfig("collections_config1.json"),
   159  			}
   160  
   161  			newLifecycleChaincode = nwo.Chaincode{
   162  				Name:              "marblesp",
   163  				Version:           "1.0",
   164  				Path:              components.Build("github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd"),
   165  				Lang:              "binary",
   166  				PackageFile:       filepath.Join(network.RootDir, "marbles-pvtdata.tar.gz"),
   167  				Label:             "marbles-private-20",
   168  				SignaturePolicy:   `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`,
   169  				CollectionsConfig: collectionConfig("collections_config1.json"),
   170  				Sequence:          "1",
   171  			}
   172  			org1Peer1 = &nwo.Peer{
   173  				Name:         "peer1",
   174  				Organization: "Org1",
   175  				Channels: []*nwo.PeerChannel{
   176  					{Name: channelID},
   177  				},
   178  			}
   179  			org2Peer1 = &nwo.Peer{
   180  				Name:         "peer1",
   181  				Organization: "Org2",
   182  				Channels: []*nwo.PeerChannel{
   183  					{Name: channelID},
   184  				},
   185  			}
   186  
   187  			process, orderer = startNetwork(network)
   188  		})
   189  
   190  		Describe("Reconciliation and pulling", func() {
   191  			var newPeerProcess ifrit.Process
   192  
   193  			BeforeEach(func() {
   194  				By("deploying legacy chaincode and adding marble1")
   195  				testChaincode = chaincode{
   196  					Chaincode: legacyChaincode,
   197  					isLegacy:  true,
   198  				}
   199  				deployChaincode(network, orderer, testChaincode)
   200  				addMarble(network, orderer, testChaincode.Name,
   201  					`{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`,
   202  					network.Peer("Org1", "peer0"),
   203  				)
   204  			})
   205  
   206  			AfterEach(func() {
   207  				if newPeerProcess != nil {
   208  					newPeerProcess.Signal(syscall.SIGTERM)
   209  					Eventually(newPeerProcess.Wait(), network.EventuallyTimeout).Should(Receive())
   210  				}
   211  			})
   212  
   213  			assertReconcileBehavior := func() {
   214  				It("disseminates private data per collections_config1", func() {
   215  					assertPvtdataPresencePerCollectionConfig1(network, testChaincode.Name, "marble1")
   216  				})
   217  
   218  				When("org3 is added to collectionMarbles via chaincode upgrade with collections_config2", func() {
   219  					It("distributes and allows access to newly added private data per collections_config2", func() {
   220  						By("upgrading the chaincode and adding marble2")
   221  						// collections_config2.json defines the access as follows:
   222  						// 1. collectionMarbles - Org1, Org2 and Org3 have access to this collection
   223  						// 2. collectionMarblePrivateDetails - Org2 and Org3 have access to this collection
   224  						// the change from collections_config1 - org3 was added to collectionMarbles
   225  						testChaincode.Version = "1.1"
   226  						testChaincode.CollectionsConfig = collectionConfig("collections_config2.json")
   227  						if !testChaincode.isLegacy {
   228  							testChaincode.Sequence = "2"
   229  						}
   230  						upgradeChaincode(network, orderer, testChaincode)
   231  						addMarble(network, orderer, testChaincode.Name,
   232  							`{"name":"marble2", "color":"yellow", "size":53, "owner":"jerry", "price":22}`,
   233  							network.Peer("Org2", "peer0"),
   234  						)
   235  
   236  						assertPvtdataPresencePerCollectionConfig2(network, testChaincode.Name, "marble2")
   237  					})
   238  				})
   239  
   240  				When("a new peer in org1 joins the channel", func() {
   241  					It("causes the new peer to pull the existing private data only for collectionMarbles", func() {
   242  						By("adding a new peer")
   243  						newPeerProcess = addPeer(network, orderer, org1Peer1)
   244  						installChaincode(network, testChaincode, org1Peer1)
   245  						network.VerifyMembership(network.Peers, channelID, "marblesp")
   246  
   247  						assertPvtdataPresencePerCollectionConfig1(network, testChaincode.Name, "marble1", org1Peer1)
   248  					})
   249  				})
   250  			}
   251  
   252  			When("chaincode in legacy lifecycle", func() {
   253  				assertReconcileBehavior()
   254  			})
   255  
   256  			When("chaincode is migrated from legacy to new lifecycle with same collection config", func() {
   257  				BeforeEach(func() {
   258  					testChaincode = chaincode{
   259  						Chaincode: newLifecycleChaincode,
   260  						isLegacy:  false,
   261  					}
   262  					nwo.EnableCapabilities(network, channelID, "Application", "V2_0", orderer, network.Peers...)
   263  					upgradeChaincode(network, orderer, testChaincode)
   264  				})
   265  				assertReconcileBehavior()
   266  			})
   267  		})
   268  
   269  		Describe("BlockToLive", func() {
   270  			var newPeerProcess ifrit.Process
   271  
   272  			AfterEach(func() {
   273  				if newPeerProcess != nil {
   274  					newPeerProcess.Signal(syscall.SIGTERM)
   275  					Eventually(newPeerProcess.Wait(), network.EventuallyTimeout).Should(Receive())
   276  				}
   277  			})
   278  
   279  			assertBlockToLiveBehavior := func() {
   280  				eligiblePeer := network.Peer("Org2", "peer0")
   281  				ccName := testChaincode.Name
   282  				By("adding three blocks")
   283  				for i := 0; i < 3; i++ {
   284  					addMarble(network, orderer, ccName, fmt.Sprintf(`{"name":"test-marble-%d", "color":"blue", "size":35, "owner":"tom", "price":99}`, i), eligiblePeer)
   285  				}
   286  
   287  				By("verifying that marble1 still not purged in collection MarblesPD")
   288  				assertPresentInCollectionMPD(network, ccName, "marble1", eligiblePeer)
   289  
   290  				By("adding one more block")
   291  				addMarble(network, orderer, ccName, `{"name":"fun-marble-3", "color":"blue", "size":35, "owner":"tom", "price":99}`, eligiblePeer)
   292  
   293  				By("verifying that marble1 purged in collection MarblesPD")
   294  				assertDoesNotExistInCollectionMPD(network, ccName, "marble1", eligiblePeer)
   295  
   296  				By("verifying that marble1 still not purged in collection Marbles")
   297  				assertPresentInCollectionM(network, ccName, "marble1", eligiblePeer)
   298  
   299  				By("adding new peer that is eligible to receive data")
   300  				newPeerProcess = addPeer(network, orderer, org2Peer1)
   301  				installChaincode(network, testChaincode, org2Peer1)
   302  				network.VerifyMembership(network.Peers, channelID, ccName)
   303  				assertPresentInCollectionM(network, ccName, "marble1", org2Peer1)
   304  				assertDoesNotExistInCollectionMPD(network, ccName, "marble1", org2Peer1)
   305  			}
   306  
   307  			When("chaincode in legacy lifecycle", func() {
   308  				It("purges private data after BTL and causes new peer not to pull the purged private data", func() {
   309  					By("deploying legacy chaincode and adding marble1")
   310  					testChaincode = chaincode{
   311  						Chaincode: legacyChaincode,
   312  						isLegacy:  true,
   313  					}
   314  
   315  					testChaincode.CollectionsConfig = collectionConfig("short_btl_config.json")
   316  					deployChaincode(network, orderer, testChaincode)
   317  					addMarble(network, orderer, testChaincode.Name, `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`, network.Peer("Org2", "peer0"))
   318  
   319  					assertBlockToLiveBehavior()
   320  				})
   321  			})
   322  
   323  			When("chaincode in new lifecycle", func() {
   324  				It("purges private data after BTL and causes new peer not to pull the purged private data", func() {
   325  					By("deploying new lifecycle chaincode and adding marble1")
   326  					testChaincode = chaincode{
   327  						Chaincode: newLifecycleChaincode,
   328  						isLegacy:  false,
   329  					}
   330  					nwo.EnableCapabilities(network, channelID, "Application", "V2_0", orderer, network.Peers...)
   331  
   332  					testChaincode.CollectionsConfig = collectionConfig("short_btl_config.json")
   333  					deployChaincode(network, orderer, testChaincode)
   334  					addMarble(network, orderer, testChaincode.Name, `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`, network.Peer("Org2", "peer0"))
   335  
   336  					assertBlockToLiveBehavior()
   337  				})
   338  			})
   339  		})
   340  
   341  		Describe("Org removal from collection", func() {
   342  			assertOrgRemovalBehavior := func() {
   343  				By("upgrading chaincode to remove org3 from collectionMarbles")
   344  				testChaincode.CollectionsConfig = collectionConfig("collections_config1.json")
   345  				testChaincode.Version = "1.1"
   346  				if !testChaincode.isLegacy {
   347  					testChaincode.Sequence = "2"
   348  				}
   349  				upgradeChaincode(network, orderer, testChaincode)
   350  				addMarble(network, orderer, testChaincode.Name, `{"name":"marble2", "color":"yellow", "size":53, "owner":"jerry", "price":22}`, network.Peer("Org2", "peer0"))
   351  				assertPvtdataPresencePerCollectionConfig1(network, testChaincode.Name, "marble2")
   352  			}
   353  
   354  			When("chaincode in legacy lifecycle", func() {
   355  				It("causes removed org not to get new data", func() {
   356  					By("deploying legacy chaincode and adding marble1")
   357  					testChaincode = chaincode{
   358  						Chaincode: legacyChaincode,
   359  						isLegacy:  true,
   360  					}
   361  					testChaincode.CollectionsConfig = collectionConfig("collections_config2.json")
   362  					deployChaincode(network, orderer, testChaincode)
   363  					addMarble(network, orderer, testChaincode.Name, `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`, network.Peer("Org2", "peer0"))
   364  					assertPvtdataPresencePerCollectionConfig2(network, testChaincode.Name, "marble1")
   365  
   366  					assertOrgRemovalBehavior()
   367  				})
   368  			})
   369  
   370  			When("chaincode in new lifecycle", func() {
   371  				It("causes removed org not to get new data", func() {
   372  					By("deploying new lifecycle chaincode and adding marble1")
   373  					testChaincode = chaincode{
   374  						Chaincode: newLifecycleChaincode,
   375  						isLegacy:  false,
   376  					}
   377  					nwo.EnableCapabilities(network, channelID, "Application", "V2_0", orderer, network.Peers...)
   378  					testChaincode.CollectionsConfig = collectionConfig("collections_config2.json")
   379  					deployChaincode(network, orderer, testChaincode)
   380  					addMarble(network, orderer, testChaincode.Name, `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`, network.Peer("Org2", "peer0"))
   381  					assertPvtdataPresencePerCollectionConfig2(network, testChaincode.Name, "marble1")
   382  
   383  					assertOrgRemovalBehavior()
   384  				})
   385  			})
   386  		})
   387  
   388  		When("migrating a chaincode from legacy lifecycle to new lifecycle", func() {
   389  			It("performs check against collection config from legacy lifecycle", func() {
   390  				By("deploying legacy chaincode")
   391  				testChaincode = chaincode{
   392  					Chaincode: legacyChaincode,
   393  					isLegacy:  true,
   394  				}
   395  				deployChaincode(network, orderer, testChaincode)
   396  				nwo.EnableCapabilities(network, channelID, "Application", "V2_0", orderer, network.Peers...)
   397  
   398  				newLifecycleChaincode.CollectionsConfig = collectionConfig("short_btl_config.json")
   399  				newLifecycleChaincode.PackageID = "test-package-id"
   400  
   401  				approveChaincodeForMyOrgExpectErr(
   402  					network,
   403  					orderer,
   404  					newLifecycleChaincode,
   405  					`the BlockToLive in an existing collection \[collectionMarblePrivateDetails\] modified. Existing value \[1000000\]`,
   406  					network.Peer("Org2", "peer0"))
   407  			})
   408  		})
   409  
   410  		Describe("marble APIs invocation and private data delivery", func() {
   411  			// call marble APIs: getMarblesByRange, transferMarble, delete, getMarbleHash, getMarblePrivateDetailsHash and verify ACL Behavior
   412  			assertMarbleAPIs := func() {
   413  				eligiblePeer := network.Peer("Org2", "peer0")
   414  				ccName := testChaincode.Name
   415  
   416  				// Verifies marble private chaincode APIs: getMarblesByRange, transferMarble, delete
   417  
   418  				By("adding five marbles")
   419  				for i := 0; i < 5; i++ {
   420  					addMarble(network, orderer, ccName, fmt.Sprintf(`{"name":"test-marble-%d", "color":"blue", "size":35, "owner":"tom", "price":99}`, i), eligiblePeer)
   421  				}
   422  
   423  				By("getting marbles by range")
   424  				assertGetMarblesByRange(network, ccName, `"test-marble-0", "test-marble-2"`, eligiblePeer)
   425  
   426  				By("transferring test-marble-0 to jerry")
   427  				transferMarble(network, orderer, ccName, `{"name":"test-marble-0", "owner":"jerry"}`, eligiblePeer)
   428  
   429  				By("verifying the new ownership of test-marble-0")
   430  				assertOwnershipInCollectionM(network, ccName, `test-marble-0`, eligiblePeer)
   431  
   432  				By("deleting test-marble-0")
   433  				deleteMarble(network, orderer, ccName, `{"name":"test-marble-0"}`, eligiblePeer)
   434  
   435  				By("verifying the deletion of test-marble-0")
   436  				assertDoesNotExistInCollectionM(network, ccName, `test-marble-0`, eligiblePeer)
   437  
   438  				// This section verifies that chaincode can return private data hash.
   439  				// Unlike private data that can only be accessed from authorized peers as defined in the collection config,
   440  				// private data hash can be queried on any peer in the channel that has the chaincode instantiated.
   441  				// When calling QueryChaincode with "getMarbleHash", the cc will return the private data hash in collectionMarbles.
   442  				// When calling QueryChaincode with "getMarblePrivateDetailsHash", the cc will return the private data hash in collectionMarblePrivateDetails.
   443  
   444  				peerList := []*nwo.Peer{
   445  					network.Peer("Org1", "peer0"),
   446  					network.Peer("Org2", "peer0"),
   447  					network.Peer("Org3", "peer0")}
   448  
   449  				By("verifying getMarbleHash is accessible from all peers that has the chaincode instantiated")
   450  				expectedBytes := util.ComputeStringHash(`{"docType":"marble","name":"test-marble-1","color":"blue","size":35,"owner":"tom"}`)
   451  				assertMarblesPrivateHashM(network, ccName, "test-marble-1", expectedBytes, peerList)
   452  
   453  				By("verifying getMarblePrivateDetailsHash is accessible from all peers that has the chaincode instantiated")
   454  				expectedBytes = util.ComputeStringHash(`{"docType":"marblePrivateDetails","name":"test-marble-1","price":99}`)
   455  				assertMarblesPrivateDetailsHashMPD(network, ccName, "test-marble-1", expectedBytes, peerList)
   456  
   457  				// collection ACL while reading private data: not allowed to non-members
   458  				// collections_config3: collectionMarblePrivateDetails - member_only_read is set to true
   459  
   460  				By("querying collectionMarblePrivateDetails on org1-peer0 by org1-user1, shouldn't have read access")
   461  				assertNoReadAccessToCollectionMPD(network, testChaincode.Name, "test-marble-1", network.Peer("Org1", "peer0"))
   462  			}
   463  
   464  			// verify DeliverWithPrivateData sends private data based on the ACL in collection config
   465  			// before and after upgrade.
   466  			assertDeliverWithPrivateDataACLBehavior := func() {
   467  				By("getting signing identity for a user in org1")
   468  				signingIdentity := getSigningIdentity(network, "Org1", "User1", "Org1MSP", "bccsp")
   469  
   470  				By("adding a marble")
   471  				peer := network.Peer("Org2", "peer0")
   472  				addMarble(network, orderer, testChaincode.Name, `{"name":"marble11", "color":"blue", "size":35, "owner":"tom", "price":99}`, peer)
   473  
   474  				By("getting the deliver event for newest block")
   475  				event := getEventFromDeliverService(network, peer, channelID, signingIdentity, 0)
   476  
   477  				By("verifying private data in deliver event contains 'collectionMarbles' only")
   478  				// it should receive pvtdata for 'collectionMarbles' only because memberOnlyRead is true
   479  				expectedKVWritesMap := map[string]map[string][]byte{
   480  					"collectionMarbles": {
   481  						"\000color~name\000blue\000marble11\000": []byte("\000"),
   482  						"marble11":                               getValueForCollectionMarbles("marble11", "blue", "tom", 35),
   483  					},
   484  				}
   485  				assertPrivateDataAsExpected(event.BlockAndPvtData.PrivateDataMap, expectedKVWritesMap)
   486  
   487  				By("upgrading chaincode with collections_config1.json where isMemberOnlyRead is false")
   488  				testChaincode.CollectionsConfig = collectionConfig("collections_config1.json")
   489  				testChaincode.Version = "1.1"
   490  				if !testChaincode.isLegacy {
   491  					testChaincode.Sequence = "2"
   492  				}
   493  				upgradeChaincode(network, orderer, testChaincode)
   494  
   495  				By("getting the deliver event for an old block committed before upgrade")
   496  				event = getEventFromDeliverService(network, peer, channelID, signingIdentity, event.BlockNum)
   497  
   498  				By("verifying the deliver event for the old block uses old config")
   499  				assertPrivateDataAsExpected(event.BlockAndPvtData.PrivateDataMap, expectedKVWritesMap)
   500  
   501  				By("adding a new marble after upgrade")
   502  				addMarble(network, orderer, testChaincode.Name,
   503  					`{"name":"marble12", "color":"blue", "size":35, "owner":"tom", "price":99}`,
   504  					network.Peer("Org1", "peer0"),
   505  				)
   506  				By("getting the deliver event for a new block committed after upgrade")
   507  				event = getEventFromDeliverService(network, peer, channelID, signingIdentity, 0)
   508  
   509  				// it should receive pvtdata for both collections because memberOnlyRead is false
   510  				By("verifying the deliver event for the new block uses new config")
   511  				expectedKVWritesMap = map[string]map[string][]byte{
   512  					"collectionMarbles": {
   513  						"\000color~name\000blue\000marble12\000": []byte("\000"),
   514  						"marble12":                               getValueForCollectionMarbles("marble12", "blue", "tom", 35),
   515  					},
   516  					"collectionMarblePrivateDetails": {
   517  						"marble12": getValueForCollectionMarblePrivateDetails("marble12", 99),
   518  					},
   519  				}
   520  				assertPrivateDataAsExpected(event.BlockAndPvtData.PrivateDataMap, expectedKVWritesMap)
   521  			}
   522  
   523  			When("chaincode in legacy lifecycle", func() {
   524  				It("calls marbles APIs and delivers private data", func() {
   525  					By("deploying legacy chaincode")
   526  					testChaincode = chaincode{
   527  						Chaincode: legacyChaincode,
   528  						isLegacy:  true,
   529  					}
   530  					testChaincode.CollectionsConfig = collectionConfig("collections_config3.json")
   531  					deployChaincode(network, orderer, testChaincode)
   532  
   533  					By("attempting to invoke chaincode from a user (org1) not in any collection member orgs (org2 and org3)")
   534  					peer2 := network.Peer("Org2", "peer0")
   535  					marbleDetailsBase64 := base64.StdEncoding.EncodeToString([]byte(`{"name":"memberonly-marble", "color":"blue", "size":35, "owner":"tom", "price":99}`))
   536  					command := commands.ChaincodeInvoke{
   537  						ChannelID:     channelID,
   538  						Orderer:       network.OrdererAddress(orderer, nwo.ListenPort),
   539  						Name:          "marblesp",
   540  						Ctor:          fmt.Sprintf(`{"Args":["initMarble"]}`),
   541  						Transient:     fmt.Sprintf(`{"marble":"%s"}`, marbleDetailsBase64),
   542  						PeerAddresses: []string{network.PeerAddress(peer2, nwo.ListenPort)},
   543  						WaitForEvent:  true,
   544  					}
   545  					peer1 := network.Peer("Org1", "peer0")
   546  					expectedErrMsg := "tx creator does not have write access permission"
   547  					invokeChaincodeWithError(network, peer1, command, expectedErrMsg)
   548  
   549  					assertMarbleAPIs()
   550  					assertDeliverWithPrivateDataACLBehavior()
   551  				})
   552  			})
   553  
   554  			When("chaincode in new lifecycle", func() {
   555  				It("calls marbles APIs and delivers private data", func() {
   556  					By("deploying new lifecycle chaincode")
   557  					testChaincode = chaincode{
   558  						Chaincode: newLifecycleChaincode,
   559  						isLegacy:  false,
   560  					}
   561  					nwo.EnableCapabilities(network, channelID, "Application", "V2_0", orderer, network.Peers...)
   562  					testChaincode.CollectionsConfig = collectionConfig("collections_config3.json")
   563  					deployChaincode(network, orderer, testChaincode)
   564  
   565  					By("attempting to invoke chaincode from a user (org1) not in any collection member orgs (org2 and org3)")
   566  					peer2 := network.Peer("Org2", "peer0")
   567  					marbleDetailsBase64 := base64.StdEncoding.EncodeToString([]byte(`{"name":"memberonly-marble", "color":"blue", "size":35, "owner":"tom", "price":99}`))
   568  					command := commands.ChaincodeInvoke{
   569  						ChannelID:     channelID,
   570  						Orderer:       network.OrdererAddress(orderer, nwo.ListenPort),
   571  						Name:          "marblesp",
   572  						Ctor:          fmt.Sprintf(`{"Args":["initMarble"]}`),
   573  						Transient:     fmt.Sprintf(`{"marble":"%s"}`, marbleDetailsBase64),
   574  						PeerAddresses: []string{network.PeerAddress(peer2, nwo.ListenPort)},
   575  						WaitForEvent:  true,
   576  					}
   577  					peer1 := network.Peer("Org1", "peer0")
   578  					expectedErrMsg := "tx creator does not have write access permission"
   579  					invokeChaincodeWithError(network, peer1, command, expectedErrMsg)
   580  
   581  					assertMarbleAPIs()
   582  					assertDeliverWithPrivateDataACLBehavior()
   583  				})
   584  			})
   585  		})
   586  
   587  		Describe("Collection Config Endorsement Policy", func() {
   588  			When("using legacy lifecycle chaincode", func() {
   589  				It("ignores the collection config endorsement policy and successfully invokes the chaincode", func() {
   590  					testChaincode = chaincode{
   591  						Chaincode: legacyChaincode,
   592  						isLegacy:  true,
   593  					}
   594  					By("setting the collection config endorsement policy to org2 or org3 peers")
   595  					testChaincode.CollectionsConfig = collectionConfig("collections_config4.json")
   596  
   597  					By("deploying legacy chaincode")
   598  					deployChaincode(network, orderer, testChaincode)
   599  
   600  					By("adding marble1 with an org 1 peer as endorser")
   601  					peer := network.Peer("Org1", "peer0")
   602  					marbleDetails := `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`
   603  					addMarble(network, orderer, testChaincode.Name, marbleDetails, peer)
   604  				})
   605  			})
   606  
   607  			When("using new lifecycle chaincode", func() {
   608  				BeforeEach(func() {
   609  					testChaincode = chaincode{
   610  						Chaincode: newLifecycleChaincode,
   611  						isLegacy:  false,
   612  					}
   613  					nwo.EnableCapabilities(network, "testchannel", "Application", "V2_0", orderer, network.Peers...)
   614  				})
   615  
   616  				When("a peer specified in the chaincode endorsement policy but not in the collection config endorsement policy is used to invoke the chaincode", func() {
   617  					It("fails validation", func() {
   618  						By("setting the collection config endorsement policy to org2 or org3 peers")
   619  						testChaincode.CollectionsConfig = collectionConfig("collections_config4.json")
   620  
   621  						By("deploying new lifecycle chaincode")
   622  						// set collection endorsement policy to org2 or org3
   623  						deployChaincode(network, orderer, testChaincode)
   624  
   625  						By("adding marble1 with an org1 peer as endorser")
   626  						peer := network.Peer("Org1", "peer0")
   627  						marbleDetails := `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`
   628  						marbleDetailsBase64 := base64.StdEncoding.EncodeToString([]byte(marbleDetails))
   629  
   630  						command := commands.ChaincodeInvoke{
   631  							ChannelID: channelID,
   632  							Orderer:   network.OrdererAddress(orderer, nwo.ListenPort),
   633  							Name:      testChaincode.Name,
   634  							Ctor:      fmt.Sprintf(`{"Args":["initMarble"]}`),
   635  							Transient: fmt.Sprintf(`{"marble":"%s"}`, marbleDetailsBase64),
   636  							PeerAddresses: []string{
   637  								network.PeerAddress(peer, nwo.ListenPort),
   638  							},
   639  							WaitForEvent: true,
   640  						}
   641  
   642  						sess, err := network.PeerUserSession(peer, "User1", command)
   643  						Expect(err).NotTo(HaveOccurred())
   644  						Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit())
   645  						Expect(sess.Err).To(gbytes.Say("ENDORSEMENT_POLICY_FAILURE"))
   646  					})
   647  				})
   648  
   649  				When("a peer specified in the collection endorsement policy but not in the chaincode endorsement policy is used to invoke the chaincode", func() {
   650  					When("the collection endorsement policy is a signature policy", func() {
   651  						It("successfully invokes the chaincode", func() {
   652  							// collection config endorsement policy specifies org2 or org3 peers for endorsement
   653  							By("setting the collection config endorsement policy to use a signature policy")
   654  							testChaincode.CollectionsConfig = collectionConfig("collections_config4.json")
   655  
   656  							By("setting the chaincode endorsement policy to org1 or org2 peers")
   657  							testChaincode.SignaturePolicy = `OR ('Org1MSP.member','Org2MSP.member')`
   658  
   659  							By("deploying new lifecycle chaincode")
   660  							// set collection endorsement policy to org2 or org3
   661  							deployChaincode(network, orderer, testChaincode)
   662  
   663  							By("adding marble1 with an org3 peer as endorser")
   664  							peer := network.Peer("Org3", "peer0")
   665  							marbleDetails := `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`
   666  							addMarble(network, orderer, testChaincode.Name, marbleDetails, peer)
   667  						})
   668  					})
   669  
   670  					When("the collection endorsement policy is a channel config policy reference", func() {
   671  						It("successfully invokes the chaincode", func() {
   672  							// collection config endorsement policy specifies channel config policy reference /Channel/Application/Readers
   673  							By("setting the collection config endorsement policy to use a channel config policy reference")
   674  							testChaincode.CollectionsConfig = collectionConfig("collections_config5.json")
   675  
   676  							By("setting the channel endorsement policy to org1 or org2 peers")
   677  							testChaincode.SignaturePolicy = `OR ('Org1MSP.member','Org2MSP.member')`
   678  
   679  							By("deploying new lifecycle chaincode")
   680  							deployChaincode(network, orderer, testChaincode)
   681  
   682  							By("adding marble1 with an org3 peer as endorser")
   683  							peer := network.Peer("Org3", "peer0")
   684  							marbleDetails := `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`
   685  							addMarble(network, orderer, testChaincode.Name, marbleDetails, peer)
   686  						})
   687  					})
   688  				})
   689  
   690  				When("the collection config endorsement policy specifies a semantically wrong, but well formed signature policy", func() {
   691  					It("fails to invoke the chaincode with an endorsement policy failure", func() {
   692  						By("setting the collection config endorsement policy to non existent org4 peers")
   693  						testChaincode.CollectionsConfig = collectionConfig("collections_config6.json")
   694  
   695  						By("deploying new lifecycle chaincode")
   696  						deployChaincode(network, orderer, testChaincode)
   697  
   698  						By("adding marble1 with an org1 peer as endorser")
   699  						peer := network.Peer("Org1", "peer0")
   700  						marbleDetails := `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`
   701  						marbleDetailsBase64 := base64.StdEncoding.EncodeToString([]byte(marbleDetails))
   702  
   703  						command := commands.ChaincodeInvoke{
   704  							ChannelID: channelID,
   705  							Orderer:   network.OrdererAddress(orderer, nwo.ListenPort),
   706  							Name:      testChaincode.Name,
   707  							Ctor:      fmt.Sprintf(`{"Args":["initMarble"]}`),
   708  							Transient: fmt.Sprintf(`{"marble":"%s"}`, marbleDetailsBase64),
   709  							PeerAddresses: []string{
   710  								network.PeerAddress(peer, nwo.ListenPort),
   711  							},
   712  							WaitForEvent: true,
   713  						}
   714  
   715  						sess, err := network.PeerUserSession(peer, "User1", command)
   716  						Expect(err).NotTo(HaveOccurred())
   717  						Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit())
   718  						Expect(sess.Err).To(gbytes.Say("ENDORSEMENT_POLICY_FAILURE"))
   719  					})
   720  				})
   721  			})
   722  		})
   723  	})
   724  })
   725  
   726  func initThreeOrgsSetup() *nwo.Network {
   727  	var err error
   728  	testDir, err := ioutil.TempDir("", "e2e-pvtdata")
   729  	Expect(err).NotTo(HaveOccurred())
   730  
   731  	client, err := docker.NewClientFromEnv()
   732  	Expect(err).NotTo(HaveOccurred())
   733  
   734  	config := nwo.FullSolo()
   735  
   736  	// add org3 with one peer
   737  	config.Organizations = append(config.Organizations, &nwo.Organization{
   738  		Name:          "Org3",
   739  		MSPID:         "Org3MSP",
   740  		Domain:        "org3.example.com",
   741  		EnableNodeOUs: true,
   742  		Users:         2,
   743  		CA:            &nwo.CA{Hostname: "ca"},
   744  	})
   745  	config.Consortiums[0].Organizations = append(config.Consortiums[0].Organizations, "Org3")
   746  	config.Profiles[1].Organizations = append(config.Profiles[1].Organizations, "Org3")
   747  	config.Peers = append(config.Peers, &nwo.Peer{
   748  		Name:         "peer0",
   749  		Organization: "Org3",
   750  		Channels: []*nwo.PeerChannel{
   751  			{Name: channelID, Anchor: true},
   752  		},
   753  	})
   754  
   755  	n := nwo.New(config, testDir, client, StartPort(), components)
   756  	n.GenerateConfigTree()
   757  
   758  	// remove peer1 from org1 and org2 so we can add it back later, we generate the config tree above
   759  	// with the two peers so the config files exist later when adding the peer back
   760  	peers := []*nwo.Peer{}
   761  	for _, p := range n.Peers {
   762  		if p.Name != "peer1" {
   763  			peers = append(peers, p)
   764  		}
   765  	}
   766  	n.Peers = peers
   767  	Expect(n.Peers).To(HaveLen(3))
   768  
   769  	return n
   770  }
   771  
   772  func startNetwork(n *nwo.Network) (ifrit.Process, *nwo.Orderer) {
   773  	n.Bootstrap()
   774  	networkRunner := n.NetworkGroupRunner()
   775  	process := ifrit.Invoke(networkRunner)
   776  	Eventually(process.Ready(), n.EventuallyTimeout).Should(BeClosed())
   777  
   778  	orderer := n.Orderer("orderer")
   779  	n.CreateAndJoinChannel(orderer, channelID)
   780  	n.UpdateChannelAnchors(orderer, channelID)
   781  
   782  	By("verifying membership")
   783  	n.VerifyMembership(n.Peers, channelID)
   784  
   785  	return process, orderer
   786  }
   787  
   788  func testCleanup(network *nwo.Network, process ifrit.Process) {
   789  	if process != nil {
   790  		process.Signal(syscall.SIGTERM)
   791  		Eventually(process.Wait(), network.EventuallyTimeout).Should(Receive())
   792  	}
   793  	if network != nil {
   794  		network.Cleanup()
   795  	}
   796  	os.RemoveAll(network.RootDir)
   797  }
   798  
   799  func collectionConfig(collConfigFile string) string {
   800  	return filepath.Join("testdata", "collection_configs", collConfigFile)
   801  }
   802  
   803  type chaincode struct {
   804  	nwo.Chaincode
   805  	isLegacy bool
   806  }
   807  
   808  func addPeer(n *nwo.Network, orderer *nwo.Orderer, peer *nwo.Peer) ifrit.Process {
   809  	process := ifrit.Invoke(n.PeerRunner(peer))
   810  	Eventually(process.Ready(), n.EventuallyTimeout).Should(BeClosed())
   811  
   812  	n.JoinChannel(channelID, orderer, peer)
   813  	ledgerHeight := nwo.GetLedgerHeight(n, n.Peers[0], channelID)
   814  	sess, err := n.PeerAdminSession(
   815  		peer,
   816  		commands.ChannelFetch{
   817  			Block:      "newest",
   818  			ChannelID:  channelID,
   819  			Orderer:    n.OrdererAddress(orderer, nwo.ListenPort),
   820  			OutputFile: filepath.Join(n.RootDir, "newest_block.pb"),
   821  		},
   822  	)
   823  	Expect(err).NotTo(HaveOccurred())
   824  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   825  	Expect(sess.Err).To(gbytes.Say(fmt.Sprintf("Received block: %d", ledgerHeight-1)))
   826  
   827  	n.Peers = append(n.Peers, peer)
   828  	nwo.WaitUntilEqualLedgerHeight(n, channelID, nwo.GetLedgerHeight(n, n.Peers[0], channelID), n.Peers...)
   829  
   830  	return process
   831  }
   832  
   833  func deployChaincode(n *nwo.Network, orderer *nwo.Orderer, chaincode chaincode) {
   834  	if chaincode.isLegacy {
   835  		nwo.DeployChaincodeLegacy(n, channelID, orderer, chaincode.Chaincode)
   836  	} else {
   837  		nwo.DeployChaincode(n, channelID, orderer, chaincode.Chaincode)
   838  	}
   839  }
   840  
   841  func upgradeChaincode(n *nwo.Network, orderer *nwo.Orderer, chaincode chaincode) {
   842  	if chaincode.isLegacy {
   843  		nwo.UpgradeChaincodeLegacy(n, channelID, orderer, chaincode.Chaincode)
   844  	} else {
   845  		nwo.DeployChaincode(n, channelID, orderer, chaincode.Chaincode)
   846  	}
   847  }
   848  
   849  func installChaincode(n *nwo.Network, chaincode chaincode, peer *nwo.Peer) {
   850  	if chaincode.isLegacy {
   851  		nwo.InstallChaincodeLegacy(n, chaincode.Chaincode, peer)
   852  	} else {
   853  		nwo.PackageAndInstallChaincode(n, chaincode.Chaincode, peer)
   854  	}
   855  }
   856  
   857  func queryChaincode(n *nwo.Network, peer *nwo.Peer, command commands.ChaincodeQuery, expectedMessage string, expectSuccess bool) {
   858  	sess, err := n.PeerUserSession(peer, "User1", command)
   859  	Expect(err).NotTo(HaveOccurred())
   860  	if expectSuccess {
   861  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   862  		Expect(sess).To(gbytes.Say(expectedMessage))
   863  	} else {
   864  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   865  		Expect(sess.Err).To(gbytes.Say(expectedMessage))
   866  	}
   867  }
   868  
   869  func invokeChaincode(n *nwo.Network, peer *nwo.Peer, command commands.ChaincodeInvoke) {
   870  	sess, err := n.PeerUserSession(peer, "User1", command)
   871  	Expect(err).NotTo(HaveOccurred())
   872  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   873  	Expect(sess.Err).To(gbytes.Say("Chaincode invoke successful."))
   874  }
   875  
   876  func invokeChaincodeWithError(n *nwo.Network, peer *nwo.Peer, command commands.ChaincodeInvoke, expectedErrMsg string) {
   877  	sess, err := n.PeerUserSession(peer, "User1", command)
   878  	Expect(err).NotTo(HaveOccurred())
   879  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(1))
   880  	Expect(sess.Err).To(gbytes.Say(expectedErrMsg))
   881  }
   882  
   883  func approveChaincodeForMyOrgExpectErr(n *nwo.Network, orderer *nwo.Orderer, chaincode nwo.Chaincode, expectedErrMsg string, peers ...*nwo.Peer) {
   884  	// used to ensure we only approve once per org
   885  	approvedOrgs := map[string]bool{}
   886  	for _, p := range peers {
   887  		if _, ok := approvedOrgs[p.Organization]; !ok {
   888  			sess, err := n.PeerAdminSession(p, commands.ChaincodeApproveForMyOrg{
   889  				ChannelID:           channelID,
   890  				Orderer:             n.OrdererAddress(orderer, nwo.ListenPort),
   891  				Name:                chaincode.Name,
   892  				Version:             chaincode.Version,
   893  				PackageID:           chaincode.PackageID,
   894  				Sequence:            chaincode.Sequence,
   895  				EndorsementPlugin:   chaincode.EndorsementPlugin,
   896  				ValidationPlugin:    chaincode.ValidationPlugin,
   897  				SignaturePolicy:     chaincode.SignaturePolicy,
   898  				ChannelConfigPolicy: chaincode.ChannelConfigPolicy,
   899  				InitRequired:        chaincode.InitRequired,
   900  				CollectionsConfig:   chaincode.CollectionsConfig,
   901  			})
   902  			Expect(err).NotTo(HaveOccurred())
   903  			Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   904  			approvedOrgs[p.Organization] = true
   905  			Eventually(sess.Err, n.EventuallyTimeout).Should(gbytes.Say(expectedErrMsg))
   906  		}
   907  	}
   908  }
   909  
   910  func addMarble(n *nwo.Network, orderer *nwo.Orderer, chaincodeName, marbleDetails string, peer *nwo.Peer) {
   911  	marbleDetailsBase64 := base64.StdEncoding.EncodeToString([]byte(marbleDetails))
   912  
   913  	command := commands.ChaincodeInvoke{
   914  		ChannelID: channelID,
   915  		Orderer:   n.OrdererAddress(orderer, nwo.ListenPort),
   916  		Name:      chaincodeName,
   917  		Ctor:      fmt.Sprintf(`{"Args":["initMarble"]}`),
   918  		Transient: fmt.Sprintf(`{"marble":"%s"}`, marbleDetailsBase64),
   919  		PeerAddresses: []string{
   920  			n.PeerAddress(peer, nwo.ListenPort),
   921  		},
   922  		WaitForEvent: true,
   923  	}
   924  	invokeChaincode(n, peer, command)
   925  	nwo.WaitUntilEqualLedgerHeight(n, channelID, nwo.GetLedgerHeight(n, peer, channelID), n.Peers...)
   926  }
   927  
   928  func deleteMarble(n *nwo.Network, orderer *nwo.Orderer, chaincodeName, marbleDelete string, peer *nwo.Peer) {
   929  	marbleDeleteBase64 := base64.StdEncoding.EncodeToString([]byte(marbleDelete))
   930  
   931  	command := commands.ChaincodeInvoke{
   932  		ChannelID: channelID,
   933  		Orderer:   n.OrdererAddress(orderer, nwo.ListenPort),
   934  		Name:      chaincodeName,
   935  		Ctor:      fmt.Sprintf(`{"Args":["delete"]}`),
   936  		Transient: fmt.Sprintf(`{"marble_delete":"%s"}`, marbleDeleteBase64),
   937  		PeerAddresses: []string{
   938  			n.PeerAddress(peer, nwo.ListenPort),
   939  		},
   940  		WaitForEvent: true,
   941  	}
   942  	invokeChaincode(n, peer, command)
   943  	nwo.WaitUntilEqualLedgerHeight(n, channelID, nwo.GetLedgerHeight(n, peer, channelID), n.Peers...)
   944  }
   945  
   946  func transferMarble(n *nwo.Network, orderer *nwo.Orderer, chaincodeName, marbleOwner string, peer *nwo.Peer) {
   947  	marbleOwnerBase64 := base64.StdEncoding.EncodeToString([]byte(marbleOwner))
   948  
   949  	command := commands.ChaincodeInvoke{
   950  		ChannelID: channelID,
   951  		Orderer:   n.OrdererAddress(orderer, nwo.ListenPort),
   952  		Name:      chaincodeName,
   953  		Ctor:      fmt.Sprintf(`{"Args":["transferMarble"]}`),
   954  		Transient: fmt.Sprintf(`{"marble_owner":"%s"}`, marbleOwnerBase64),
   955  		PeerAddresses: []string{
   956  			n.PeerAddress(peer, nwo.ListenPort),
   957  		},
   958  		WaitForEvent: true,
   959  	}
   960  	invokeChaincode(n, peer, command)
   961  	nwo.WaitUntilEqualLedgerHeight(n, channelID, nwo.GetLedgerHeight(n, peer, channelID), n.Peers...)
   962  }
   963  
   964  func assertPvtdataPresencePerCollectionConfig1(n *nwo.Network, chaincodeName, marbleName string, peers ...*nwo.Peer) {
   965  	if len(peers) == 0 {
   966  		peers = n.Peers
   967  	}
   968  	for _, peer := range peers {
   969  		switch peer.Organization {
   970  
   971  		case "Org1":
   972  			assertPresentInCollectionM(n, chaincodeName, marbleName, peer)
   973  			assertNotPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
   974  
   975  		case "Org2":
   976  			assertPresentInCollectionM(n, chaincodeName, marbleName, peer)
   977  			assertPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
   978  
   979  		case "Org3":
   980  			assertNotPresentInCollectionM(n, chaincodeName, marbleName, peer)
   981  			assertPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
   982  		}
   983  	}
   984  }
   985  
   986  func assertPvtdataPresencePerCollectionConfig2(n *nwo.Network, chaincodeName, marbleName string, peers ...*nwo.Peer) {
   987  	if len(peers) == 0 {
   988  		peers = n.Peers
   989  	}
   990  	for _, peer := range peers {
   991  		switch peer.Organization {
   992  
   993  		case "Org1":
   994  			assertPresentInCollectionM(n, chaincodeName, marbleName, peer)
   995  			assertNotPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
   996  
   997  		case "Org2", "Org3":
   998  			assertPresentInCollectionM(n, chaincodeName, marbleName, peer)
   999  			assertPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
  1000  		}
  1001  	}
  1002  }
  1003  
  1004  func assertPvtdataPresencePerCollectionConfig7(n *nwo.Network, chaincodeName, marbleName string, excludedPeer *nwo.Peer, peers ...*nwo.Peer) {
  1005  	if len(peers) == 0 {
  1006  		peers = n.Peers
  1007  	}
  1008  	collectionMPresence := 0
  1009  	collectionMPDPresence := 0
  1010  	for _, peer := range peers {
  1011  		// exclude the peer that invoked originally and count number of peers disseminated to
  1012  		if peer != excludedPeer {
  1013  			switch peer.Organization {
  1014  
  1015  			case "Org1":
  1016  				collectionMPresence += checkPresentInCollectionM(n, chaincodeName, marbleName, peer)
  1017  				assertNotPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
  1018  
  1019  			case "Org2":
  1020  				collectionMPresence += checkPresentInCollectionM(n, chaincodeName, marbleName, peer)
  1021  				collectionMPDPresence += checkPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
  1022  
  1023  			case "Org3":
  1024  				assertNotPresentInCollectionM(n, chaincodeName, marbleName, peer)
  1025  				collectionMPDPresence += checkPresentInCollectionMPD(n, chaincodeName, marbleName, peer)
  1026  			}
  1027  		}
  1028  	}
  1029  	Expect(collectionMPresence).To(Equal(1))
  1030  	Expect(collectionMPDPresence).To(Equal(1))
  1031  
  1032  }
  1033  
  1034  // assertGetMarblesByRange asserts that
  1035  func assertGetMarblesByRange(n *nwo.Network, chaincodeName, marbleRange string, peer *nwo.Peer) {
  1036  	query := fmt.Sprintf(`{"Args":["getMarblesByRange", %s]}`, marbleRange)
  1037  	expectedMsg := `\Q[{"Key":"test-marble-0", "Record":{"docType":"marble","name":"test-marble-0","color":"blue","size":35,"owner":"tom"}},{"Key":"test-marble-1", "Record":{"docType":"marble","name":"test-marble-1","color":"blue","size":35,"owner":"tom"}}]\E`
  1038  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, true, peer)
  1039  }
  1040  
  1041  // assertPresentInCollectionM asserts that the private data for given marble is present in collection
  1042  // 'readMarble' at the given peers
  1043  func assertPresentInCollectionM(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1044  	query := fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName)
  1045  	expectedMsg := fmt.Sprintf(`{"docType":"marble","name":"%s"`, marbleName)
  1046  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, true, peerList...)
  1047  }
  1048  
  1049  // assertPresentInCollectionMPD asserts that the private data for given marble is present
  1050  // in collection 'readMarblePrivateDetails' at the given peers
  1051  func assertPresentInCollectionMPD(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1052  	query := fmt.Sprintf(`{"Args":["readMarblePrivateDetails","%s"]}`, marbleName)
  1053  	expectedMsg := fmt.Sprintf(`{"docType":"marblePrivateDetails","name":"%s"`, marbleName)
  1054  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, true, peerList...)
  1055  }
  1056  
  1057  // checkPresentInCollectionM checks then number of peers that have the private data for given marble
  1058  // in collection 'readMarble'
  1059  func checkPresentInCollectionM(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) int {
  1060  	query := fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName)
  1061  	expectedMsg := fmt.Sprintf(`{"docType":"marble","name":"%s"`, marbleName)
  1062  	command := commands.ChaincodeQuery{
  1063  		ChannelID: channelID,
  1064  		Name:      chaincodeName,
  1065  		Ctor:      query,
  1066  	}
  1067  	present := 0
  1068  	for _, peer := range peerList {
  1069  		sess, err := n.PeerUserSession(peer, "User1", command)
  1070  		Expect(err).NotTo(HaveOccurred())
  1071  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
  1072  		if bytes.Contains(sess.Buffer().Contents(), []byte(expectedMsg)) {
  1073  			present++
  1074  		}
  1075  	}
  1076  	return present
  1077  }
  1078  
  1079  // checkPresentInCollectionMPD checks the number of peers that have the private data for given marble
  1080  // in collection 'readMarblePrivateDetails'
  1081  func checkPresentInCollectionMPD(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) int {
  1082  	query := fmt.Sprintf(`{"Args":["readMarblePrivateDetails","%s"]}`, marbleName)
  1083  	expectedMsg := fmt.Sprintf(`{"docType":"marblePrivateDetails","name":"%s"`, marbleName)
  1084  	command := commands.ChaincodeQuery{
  1085  		ChannelID: channelID,
  1086  		Name:      chaincodeName,
  1087  		Ctor:      query,
  1088  	}
  1089  	present := 0
  1090  	for _, peer := range peerList {
  1091  		sess, err := n.PeerUserSession(peer, "User1", command)
  1092  		Expect(err).NotTo(HaveOccurred())
  1093  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
  1094  		if bytes.Contains(sess.Buffer().Contents(), []byte(expectedMsg)) {
  1095  			present++
  1096  		}
  1097  	}
  1098  	return present
  1099  }
  1100  
  1101  // assertNotPresentInCollectionM asserts that the private data for given marble is NOT present
  1102  // in collection 'readMarble' at the given peers
  1103  func assertNotPresentInCollectionM(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1104  	query := fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName)
  1105  	expectedMsg := "private data matching public hash version is not available"
  1106  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, false, peerList...)
  1107  }
  1108  
  1109  // assertNotPresentInCollectionMPD asserts that the private data for given marble is NOT present
  1110  // in collection 'readMarblePrivateDetails' at the given peers
  1111  func assertNotPresentInCollectionMPD(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1112  	query := fmt.Sprintf(`{"Args":["readMarblePrivateDetails","%s"]}`, marbleName)
  1113  	expectedMsg := "private data matching public hash version is not available"
  1114  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, false, peerList...)
  1115  }
  1116  
  1117  // assertDoesNotExistInCollectionM asserts that the private data for given marble
  1118  // does not exist in collection 'readMarble' (i.e., is never created/has been deleted/has been purged)
  1119  func assertDoesNotExistInCollectionM(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1120  	query := fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName)
  1121  	expectedMsg := "Marble does not exist"
  1122  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, false, peerList...)
  1123  }
  1124  
  1125  // assertDoesNotExistInCollectionMPD asserts that the private data for given marble
  1126  // does not exist in collection 'readMarblePrivateDetails' (i.e., is never created/has been deleted/has been purged)
  1127  func assertDoesNotExistInCollectionMPD(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1128  	query := fmt.Sprintf(`{"Args":["readMarblePrivateDetails","%s"]}`, marbleName)
  1129  	expectedMsg := "Marble private details does not exist"
  1130  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, false, peerList...)
  1131  }
  1132  
  1133  // assertOwnershipInCollectionM asserts that the private data for given marble is present
  1134  // in collection 'readMarble' at the given peers
  1135  func assertOwnershipInCollectionM(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1136  	query := fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName)
  1137  	expectedMsg := fmt.Sprintf(`{"docType":"marble","name":"test-marble-0","color":"blue","size":35,"owner":"jerry"}`)
  1138  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, true, peerList...)
  1139  }
  1140  
  1141  // assertNoReadAccessToCollectionMPD asserts that the orgs of the given peers do not have
  1142  // read access to private data for the collection readMarblePrivateDetails
  1143  func assertNoReadAccessToCollectionMPD(n *nwo.Network, chaincodeName, marbleName string, peerList ...*nwo.Peer) {
  1144  	query := fmt.Sprintf(`{"Args":["readMarblePrivateDetails","%s"]}`, marbleName)
  1145  	expectedMsg := "tx creator does not have read access permission"
  1146  	queryChaincodePerPeer(n, query, chaincodeName, expectedMsg, false, peerList...)
  1147  }
  1148  
  1149  func queryChaincodePerPeer(n *nwo.Network, query, chaincodeName, expectedMsg string, expectSuccess bool, peerList ...*nwo.Peer) {
  1150  	command := commands.ChaincodeQuery{
  1151  		ChannelID: channelID,
  1152  		Name:      chaincodeName,
  1153  		Ctor:      query,
  1154  	}
  1155  	for _, peer := range peerList {
  1156  		queryChaincode(n, peer, command, expectedMsg, expectSuccess)
  1157  	}
  1158  }
  1159  
  1160  // assertMarblesPrivateHashM asserts that getMarbleHash is accessible from all peers that has the chaincode instantiated
  1161  func assertMarblesPrivateHashM(n *nwo.Network, chaincodeName, marbleName string, expectedBytes []byte, peerList []*nwo.Peer) {
  1162  	query := fmt.Sprintf(`{"Args":["getMarbleHash","%s"]}`, marbleName)
  1163  	verifyPvtdataHash(n, query, chaincodeName, peerList, expectedBytes)
  1164  }
  1165  
  1166  // assertMarblesPrivateDetailsHashMPD asserts that getMarblePrivateDetailsHash is accessible from all peers that has the chaincode instantiated
  1167  func assertMarblesPrivateDetailsHashMPD(n *nwo.Network, chaincodeName, marbleName string, expectedBytes []byte, peerList []*nwo.Peer) {
  1168  	query := fmt.Sprintf(`{"Args":["getMarblePrivateDetailsHash","%s"]}`, marbleName)
  1169  	verifyPvtdataHash(n, query, chaincodeName, peerList, expectedBytes)
  1170  }
  1171  
  1172  // verifyPvtdataHash verifies the private data hash matches the expected bytes.
  1173  // Cannot reuse verifyAccess because the hash bytes are not valid utf8 causing gbytes.Say to fail.
  1174  func verifyPvtdataHash(n *nwo.Network, query, chaincodeName string, peers []*nwo.Peer, expected []byte) {
  1175  	command := commands.ChaincodeQuery{
  1176  		ChannelID: channelID,
  1177  		Name:      chaincodeName,
  1178  		Ctor:      query,
  1179  	}
  1180  
  1181  	for _, peer := range peers {
  1182  		sess, err := n.PeerUserSession(peer, "User1", command)
  1183  		Expect(err).NotTo(HaveOccurred())
  1184  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
  1185  		actual := sess.Buffer().Contents()
  1186  		// verify actual bytes contain expected bytes - cannot use equal because session may contain extra bytes
  1187  		Expect(bytes.Contains(actual, expected)).To(Equal(true))
  1188  	}
  1189  }
  1190  
  1191  // deliverEvent contains the response and related info from a DeliverWithPrivateData call
  1192  type deliverEvent struct {
  1193  	BlockAndPvtData *pb.BlockAndPrivateData
  1194  	BlockNum        uint64
  1195  	Err             error
  1196  }
  1197  
  1198  // getEventFromDeliverService send a request to DeliverWithPrivateData grpc service
  1199  // and receive the response
  1200  func getEventFromDeliverService(network *nwo.Network, peer *nwo.Peer, channelID string, signingIdentity msp.SigningIdentity, blockNum uint64) *deliverEvent {
  1201  	ctx, cancelFunc1 := context.WithTimeout(context.Background(), network.EventuallyTimeout)
  1202  	defer cancelFunc1()
  1203  	eventCh, conn := registerForDeliverEvent(ctx, network, peer, channelID, signingIdentity, blockNum)
  1204  	defer conn.Close()
  1205  	event := &deliverEvent{}
  1206  	Eventually(eventCh, network.EventuallyTimeout).Should(Receive(event))
  1207  	Expect(event.Err).NotTo(HaveOccurred())
  1208  	return event
  1209  }
  1210  
  1211  func registerForDeliverEvent(
  1212  	ctx context.Context,
  1213  	network *nwo.Network,
  1214  	peer *nwo.Peer,
  1215  	channelID string,
  1216  	signingIdentity msp.SigningIdentity,
  1217  	blockNum uint64,
  1218  ) (<-chan deliverEvent, *grpc.ClientConn) {
  1219  	// create a comm.GRPCClient
  1220  	tlsRootCertFile := filepath.Join(network.PeerLocalTLSDir(peer), "ca.crt")
  1221  	caPEM, err := ioutil.ReadFile(tlsRootCertFile)
  1222  	Expect(err).NotTo(HaveOccurred())
  1223  	clientConfig := comm.ClientConfig{Timeout: 10 * time.Second}
  1224  	clientConfig.SecOpts = comm.SecureOptions{
  1225  		UseTLS:            true,
  1226  		ServerRootCAs:     [][]byte{caPEM},
  1227  		RequireClientCert: false,
  1228  	}
  1229  	grpcClient, err := comm.NewGRPCClient(clientConfig)
  1230  	Expect(err).NotTo(HaveOccurred())
  1231  	// create a client for DeliverWithPrivateData
  1232  	address := network.PeerAddress(peer, nwo.ListenPort)
  1233  	conn, err := grpcClient.NewConnection(address)
  1234  	Expect(err).NotTo(HaveOccurred())
  1235  	dp, err := pb.NewDeliverClient(conn).DeliverWithPrivateData(ctx)
  1236  	Expect(err).NotTo(HaveOccurred())
  1237  	// send a deliver request
  1238  	envelope, err := createDeliverEnvelope(channelID, signingIdentity, blockNum)
  1239  	Expect(err).NotTo(HaveOccurred())
  1240  	err = dp.Send(envelope)
  1241  	dp.CloseSend()
  1242  	Expect(err).NotTo(HaveOccurred())
  1243  	// create a goroutine to receive the response in a separate thread
  1244  	eventCh := make(chan deliverEvent, 1)
  1245  	go receiveDeliverResponse(dp, address, eventCh)
  1246  
  1247  	return eventCh, conn
  1248  }
  1249  
  1250  func getSigningIdentity(network *nwo.Network, org, user, mspID, mspType string) msp.SigningIdentity {
  1251  	peerForOrg := network.Peer(org, "peer0")
  1252  	mspConfigPath := network.PeerUserMSPDir(peerForOrg, user)
  1253  	mspInstance, err := loadLocalMSPAt(mspConfigPath, mspID, mspType)
  1254  	Expect(err).NotTo(HaveOccurred())
  1255  
  1256  	signingIdentity, err := mspInstance.GetDefaultSigningIdentity()
  1257  	Expect(err).NotTo(HaveOccurred())
  1258  	return signingIdentity
  1259  }
  1260  
  1261  // loadLocalMSPAt loads an MSP whose configuration is stored at 'dir', and whose
  1262  // id and type are the passed as arguments.
  1263  func loadLocalMSPAt(dir, id, mspType string) (msp.MSP, error) {
  1264  	if mspType != "bccsp" {
  1265  		return nil, errors.Errorf("invalid msp type, expected 'bccsp', got %s", mspType)
  1266  	}
  1267  	conf, err := msp.GetLocalMspConfig(dir, nil, id)
  1268  	if err != nil {
  1269  		return nil, err
  1270  	}
  1271  	ks, err := sw.NewFileBasedKeyStore(nil, filepath.Join(dir, "keystore"), true)
  1272  	if err != nil {
  1273  		return nil, err
  1274  	}
  1275  	cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
  1276  	if err != nil {
  1277  		return nil, err
  1278  	}
  1279  	thisMSP, err := msp.NewBccspMspWithKeyStore(msp.MSPv1_0, ks, cryptoProvider)
  1280  	if err != nil {
  1281  		return nil, err
  1282  	}
  1283  	err = thisMSP.Setup(conf)
  1284  	if err != nil {
  1285  		return nil, err
  1286  	}
  1287  	return thisMSP, nil
  1288  }
  1289  
  1290  // receiveDeliverResponse expects to receive the BlockAndPrivateData response for the requested block.
  1291  func receiveDeliverResponse(dp pb.Deliver_DeliverWithPrivateDataClient, address string, eventCh chan<- deliverEvent) error {
  1292  	event := deliverEvent{}
  1293  
  1294  	resp, err := dp.Recv()
  1295  	if err != nil {
  1296  		event.Err = errors.WithMessagef(err, "error receiving deliver response from peer %s\n", address)
  1297  	}
  1298  	switch r := resp.Type.(type) {
  1299  	case *pb.DeliverResponse_BlockAndPrivateData:
  1300  		event.BlockAndPvtData = r.BlockAndPrivateData
  1301  		event.BlockNum = r.BlockAndPrivateData.Block.Header.Number
  1302  	case *pb.DeliverResponse_Status:
  1303  		event.Err = errors.Errorf("deliver completed with status (%s) before DeliverResponse_BlockAndPrivateData received from peer %s", r.Status, address)
  1304  	default:
  1305  		event.Err = errors.Errorf("received unexpected response type (%T) from peer %s", r, address)
  1306  	}
  1307  
  1308  	select {
  1309  	case eventCh <- event:
  1310  	default:
  1311  	}
  1312  	return nil
  1313  }
  1314  
  1315  // createDeliverEnvelope creates a deliver request based on the block number.
  1316  // blockNum=0 means newest block
  1317  func createDeliverEnvelope(channelID string, signingIdentity msp.SigningIdentity, blockNum uint64) (*cb.Envelope, error) {
  1318  	creator, err := signingIdentity.Serialize()
  1319  	if err != nil {
  1320  		return nil, err
  1321  	}
  1322  	header, err := createHeader(cb.HeaderType_DELIVER_SEEK_INFO, channelID, creator)
  1323  	if err != nil {
  1324  		return nil, err
  1325  	}
  1326  
  1327  	// if blockNum is not greater than 0, seek the newest block
  1328  	var seekInfo *ab.SeekInfo
  1329  	if blockNum > 0 {
  1330  		seekInfo = &ab.SeekInfo{
  1331  			Start: &ab.SeekPosition{
  1332  				Type: &ab.SeekPosition_Specified{
  1333  					Specified: &ab.SeekSpecified{Number: blockNum},
  1334  				},
  1335  			},
  1336  			Stop: &ab.SeekPosition{
  1337  				Type: &ab.SeekPosition_Specified{
  1338  					Specified: &ab.SeekSpecified{Number: blockNum},
  1339  				},
  1340  			},
  1341  		}
  1342  	} else {
  1343  		seekInfo = &ab.SeekInfo{
  1344  			Start: &ab.SeekPosition{
  1345  				Type: &ab.SeekPosition_Newest{
  1346  					Newest: &ab.SeekNewest{},
  1347  				},
  1348  			},
  1349  			Stop: &ab.SeekPosition{
  1350  				Type: &ab.SeekPosition_Newest{
  1351  					Newest: &ab.SeekNewest{},
  1352  				},
  1353  			},
  1354  		}
  1355  	}
  1356  
  1357  	// create the envelope
  1358  	raw := protoutil.MarshalOrPanic(seekInfo)
  1359  	payload := &cb.Payload{
  1360  		Header: header,
  1361  		Data:   raw,
  1362  	}
  1363  	payloadBytes := protoutil.MarshalOrPanic(payload)
  1364  	signature, err := signingIdentity.Sign(payloadBytes)
  1365  	if err != nil {
  1366  		return nil, err
  1367  	}
  1368  	return &cb.Envelope{
  1369  		Payload:   payloadBytes,
  1370  		Signature: signature,
  1371  	}, nil
  1372  }
  1373  
  1374  func createHeader(txType cb.HeaderType, channelID string, creator []byte) (*cb.Header, error) {
  1375  	ts, err := ptypes.TimestampProto(time.Now())
  1376  	if err != nil {
  1377  		return nil, err
  1378  	}
  1379  	nonce, err := crypto.GetRandomNonce()
  1380  	if err != nil {
  1381  		return nil, err
  1382  	}
  1383  	chdr := &cb.ChannelHeader{
  1384  		Type:      int32(txType),
  1385  		ChannelId: channelID,
  1386  		TxId:      protoutil.ComputeTxID(nonce, creator),
  1387  		Epoch:     0,
  1388  		Timestamp: ts,
  1389  	}
  1390  	chdrBytes := protoutil.MarshalOrPanic(chdr)
  1391  
  1392  	shdr := &cb.SignatureHeader{
  1393  		Creator: creator,
  1394  		Nonce:   nonce,
  1395  	}
  1396  	shdrBytes := protoutil.MarshalOrPanic(shdr)
  1397  	header := &cb.Header{
  1398  		ChannelHeader:   chdrBytes,
  1399  		SignatureHeader: shdrBytes,
  1400  	}
  1401  	return header, nil
  1402  }
  1403  
  1404  // verify collection names and pvtdataMap match expectedKVWritesMap
  1405  func assertPrivateDataAsExpected(pvtdataMap map[uint64]*rwset.TxPvtReadWriteSet, expectedKVWritesMap map[string]map[string][]byte) {
  1406  	// In the test, each block has only 1 tx, so txSeqInBlock is 0
  1407  	txPvtRwset := pvtdataMap[uint64(0)]
  1408  	Expect(txPvtRwset.NsPvtRwset).To(HaveLen(1))
  1409  	Expect(txPvtRwset.NsPvtRwset[0].Namespace).To(Equal("marblesp"))
  1410  	Expect(txPvtRwset.NsPvtRwset[0].CollectionPvtRwset).To(HaveLen(len(expectedKVWritesMap)))
  1411  
  1412  	// verify the collections returned in private data have expected collection names and kvRwset.Writes
  1413  	for _, col := range txPvtRwset.NsPvtRwset[0].CollectionPvtRwset {
  1414  		Expect(expectedKVWritesMap).To(HaveKey(col.CollectionName))
  1415  		expectedKvWrites := expectedKVWritesMap[col.CollectionName]
  1416  		kvRwset := kvrwset.KVRWSet{}
  1417  		err := proto.Unmarshal(col.GetRwset(), &kvRwset)
  1418  		Expect(err).NotTo(HaveOccurred())
  1419  		Expect(kvRwset.Writes).To(HaveLen(len(expectedKvWrites)))
  1420  		for _, kvWrite := range kvRwset.Writes {
  1421  			Expect(expectedKvWrites).To(HaveKey(kvWrite.Key))
  1422  			Expect(kvWrite.Value).To(Equal(expectedKvWrites[kvWrite.Key]))
  1423  		}
  1424  	}
  1425  }
  1426  
  1427  func getValueForCollectionMarbles(marbleName, color, owner string, size int) []byte {
  1428  	marbleJSONasString := `{"docType":"marble","name":"` + marbleName + `","color":"` + color + `","size":` + strconv.Itoa(size) + `,"owner":"` + owner + `"}`
  1429  	return []byte(marbleJSONasString)
  1430  }
  1431  
  1432  func getValueForCollectionMarblePrivateDetails(marbleName string, price int) []byte {
  1433  	marbleJSONasString := `{"docType":"marblePrivateDetails","name":"` + marbleName + `","price":` + strconv.Itoa(price) + `}`
  1434  	return []byte(marbleJSONasString)
  1435  }