@@ -7,18 +7,22 @@ SPDX-License-Identifier: Apache-2.0
7
7
package e2e
8
8
9
9
import (
10
+ "encoding/json"
10
11
"fmt"
11
12
"io/ioutil"
12
13
"os"
13
14
"path/filepath"
15
+ "strings"
14
16
"time"
15
17
16
18
"github.com/hyperledger/fabric/integration/pvtdata/helpers"
17
19
"github.com/hyperledger/fabric/integration/pvtdata/runner"
18
20
"github.com/hyperledger/fabric/integration/pvtdata/world"
21
+ "github.com/hyperledger/fabric/protos/common"
19
22
. "github.com/onsi/ginkgo"
20
23
. "github.com/onsi/gomega"
21
24
"github.com/onsi/gomega/gbytes"
25
+ "github.com/tedsuo/ifrit"
22
26
23
27
"github.com/fsouza/go-dockerclient"
24
28
)
@@ -221,6 +225,111 @@ var _ = Describe("PrivateData-EndToEnd", func() {
221
225
verifyAccessFailed (d .Chaincode .Name , d .Channel , `{"Args":["readMarble","marble2"]}` , adminPeer , "Failed to get state for marble2" )
222
226
})
223
227
})
228
+
229
+ Describe ("collection config BlockToLive is respected" , func () {
230
+ BeforeEach (func () {
231
+ var err error
232
+ testDir , err = ioutil .TempDir ("" , "e2e-pvtdata" )
233
+ Expect (err ).NotTo (HaveOccurred ())
234
+ w = world .GenerateBasicConfig ("solo" , 1 , 3 , testDir , components )
235
+
236
+ // instantiate the chaincode with two collections:
237
+ // collectionMarbles - with blockToLive=1000000
238
+ // collectionMarblePrivateDetails - with blockToLive=3
239
+ // will use "readMarble" to read from the first one and "readMarblePrivateDetails" from the second one.
240
+ // need to make sure data is purged only on the second collection
241
+ d = world.Deployment {
242
+ Channel : "testchannel" ,
243
+ Chaincode : world.Chaincode {
244
+ Name : "marblesp" ,
245
+ Version : "1.0" ,
246
+ Path : "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd" ,
247
+ ExecPath : os .Getenv ("PATH" ),
248
+ CollectionsConfigPath : filepath .Join ("testdata" , "collection_configs" , "short_btl_config.json" ),
249
+ },
250
+ InitArgs : `{"Args":["init"]}` ,
251
+ Policy : `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')` ,
252
+ Orderer : "127.0.0.1:7050" ,
253
+ }
254
+
255
+ w .SetupWorld (d )
256
+
257
+ By ("setting up all anchor peers" )
258
+ setAnchorsPeerForAllOrgs (1 , 3 , d , w .Rootpath )
259
+
260
+ By ("verify membership was built using discovery service" )
261
+ expectedDiscoveredPeers = []helpers.DiscoveredPeer {
262
+ {MSPID : "Org1MSP" , LedgerHeight : 0 , Endpoint : "0.0.0.0:7051" , Identity : "" , Chaincodes : []string {}},
263
+ {MSPID : "Org2MSP" , LedgerHeight : 0 , Endpoint : "0.0.0.0:8051" , Identity : "" , Chaincodes : []string {}},
264
+ {MSPID : "Org3MSP" , LedgerHeight : 0 , Endpoint : "0.0.0.0:9051" , Identity : "" , Chaincodes : []string {}},
265
+ }
266
+ verifyMembership (w , d , expectedDiscoveredPeers )
267
+
268
+ By ("invoking initMarble function of the chaincode" )
269
+ adminPeer := getPeer (0 , 1 , w .Rootpath )
270
+ adminRunner := adminPeer .InvokeChaincode (d .Chaincode .Name , d .Channel , `{"Args":["initMarble","marble1","blue","35","tom","99"]}` , d .Orderer )
271
+ err = helpers .Execute (adminRunner )
272
+ Expect (err ).NotTo (HaveOccurred ())
273
+ Expect (adminRunner .Err ()).To (gbytes .Say ("Chaincode invoke successful." ))
274
+ })
275
+
276
+ AfterEach (func () {
277
+ if w != nil {
278
+ w .Close (d )
279
+ }
280
+ os .RemoveAll (testDir )
281
+ })
282
+
283
+ It ("verifies private data is purged after BTL has passed and new peer doesn't pull private data that was purged" , func () {
284
+ adminPeer := getPeer (0 , 2 , testDir )
285
+ By ("create 4 blocks to reach BTL threshold and have marble1 private data purged" )
286
+ initialLedgerHeight := getLedgerHeight (0 , 2 , d .Channel , testDir )
287
+ ledgerHeight := initialLedgerHeight
288
+ verifyAccess (d .Chaincode .Name , d .Channel , `{"Args":["readMarblePrivateDetails","marble1"]}` , []* runner.Peer {adminPeer }, `{"docType":"marblePrivateDetails","name":"marble1","price":99}` )
289
+ for i := 2 ; ledgerHeight < initialLedgerHeight + 4 ; i ++ {
290
+ By (fmt .Sprintf ("created %d blocks, still haven't reached BTL, should have access to private data" , ledgerHeight - initialLedgerHeight ))
291
+ adminRunner := adminPeer .InvokeChaincode (d .Chaincode .Name , d .Channel , fmt .Sprintf (`{"Args":["initMarble","marble%d","blue%d","3%d","tom","9%d"]}` , i , i , i , i ), d .Orderer )
292
+ err := helpers .Execute (adminRunner )
293
+ Expect (err ).NotTo (HaveOccurred ())
294
+ Expect (adminRunner .Err ()).To (gbytes .Say ("Chaincode invoke successful." ))
295
+ ledgerHeight = getLedgerHeight (0 , 2 , d .Channel , testDir )
296
+ }
297
+ By ("querying collectionMarbles by peer0.org2, marble1 should still be available" )
298
+ verifyAccess (d .Chaincode .Name , d .Channel , `{"Args":["readMarble","marble1"]}` , []* runner.Peer {adminPeer }, `{"docType":"marble","name":"marble1","color":"blue","size":35,"owner":"tom"}` )
299
+ By ("querying collectionMarblePrivateDetails by peer0.org2, marble1 should have been purged" )
300
+ verifyAccessFailed (d .Chaincode .Name , d .Channel , `{"Args":["readMarblePrivateDetails","marble1"]}` , adminPeer , "Marble private details does not exist: marble1" )
301
+
302
+ By ("spawn a new peer peer1.org2" )
303
+ spawnNewPeer (w , 1 , 2 )
304
+ adminPeer = getPeer (1 , 2 , testDir )
305
+ EventuallyWithOffset (1 , func () (* gbytes.Buffer , error ) {
306
+ adminRunner := adminPeer .FetchChannel (d .Channel , filepath .Join (w .Rootpath , "peer1.org2.example.com" , fmt .Sprintf ("%s_block.pb" , d .Channel )), "0" , d .Orderer )
307
+ err := helpers .Execute (adminRunner )
308
+ return adminRunner .Err (), err
309
+ }).Should (gbytes .Say ("Received block: 0" ))
310
+
311
+ By ("join peer1.org2 to the channel" )
312
+ EventuallyWithOffset (1 , func () (* gbytes.Buffer , error ) {
313
+ adminRunner := adminPeer .JoinChannel (filepath .Join (w .Rootpath , "peer1.org2.example.com" , fmt .Sprintf ("%s_block.pb" , d .Channel )))
314
+ err := helpers .Execute (adminRunner )
315
+ return adminRunner .Err (), err
316
+ }).Should (gbytes .Say ("Successfully submitted proposal to join channel" ))
317
+
318
+ By ("fetch latest blocks to peer1.org2" )
319
+ EventuallyWithOffset (1 , func () (* gbytes.Buffer , error ) {
320
+ adminRunner := adminPeer .FetchChannel (d .Channel , filepath .Join (w .Rootpath , "peer1.org2.example.com" , fmt .Sprintf ("%s_block.pb" , d .Channel )), "newest" , d .Orderer )
321
+ err := helpers .Execute (adminRunner )
322
+ return adminRunner .Err (), err
323
+ }).Should (gbytes .Say ("Received block: 9" ))
324
+
325
+ By ("install the chaincode on peer1.org2 in order to query it" )
326
+ adminPeer .InstallChaincode ("marblesp" , "1.0" , "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd" )
327
+
328
+ By ("query peer1.org2, verify marble1 exist in collectionMarbles and private data doesn't exist" )
329
+ verifyAccess (d .Chaincode .Name , d .Channel , `{"Args":["readMarble","marble1"]}` , []* runner.Peer {adminPeer }, `{"docType":"marble","name":"marble1","color":"blue","size":35,"owner":"tom"}` )
330
+ verifyAccessFailed (d .Chaincode .Name , d .Channel , `{"Args":["readMarblePrivateDetails","marble1"]}` , adminPeer , "Marble private details does not exist: marble1" )
331
+ })
332
+ })
224
333
})
225
334
226
335
func getPeer (peer int , org int , testDir string ) * runner.Peer {
@@ -231,6 +340,18 @@ func getPeer(peer int, org int, testDir string) *runner.Peer {
231
340
return adminPeer
232
341
}
233
342
343
+ func getLedgerHeight (peer int , org int , channel string , testDir string ) int {
344
+ adminPeer := getPeer (peer , org , testDir )
345
+ adminRunner := adminPeer .GetChannelInfo (channel )
346
+ err := helpers .Execute (adminRunner )
347
+ Expect (err ).NotTo (HaveOccurred ())
348
+
349
+ channelInfoStr := strings .TrimPrefix (string (adminRunner .Buffer ().Contents ()[:]), "Blockchain info:" )
350
+ var channelInfo = common.BlockchainInfo {}
351
+ json .Unmarshal ([]byte (channelInfoStr ), & channelInfo )
352
+ return int (channelInfo .Height )
353
+ }
354
+
234
355
func setAnchorsPeerForAllOrgs (numPeers int , numOrgs int , d world.Deployment , testDir string ) {
235
356
for orgIndex := 1 ; orgIndex <= numOrgs ; orgIndex ++ {
236
357
for peerIndex := 0 ; peerIndex < numPeers ; peerIndex ++ {
@@ -271,6 +392,28 @@ func verifyAccessFailed(ccname string, channel string, args string, peer *runner
271
392
}, time .Minute ).Should (gbytes .Say (expectedFailureMessage ))
272
393
}
273
394
395
+ func spawnNewPeer (w * world.World , peerIndex int , orgIndex int ) {
396
+ peerName := fmt .Sprintf ("peer%d.org%d.example.com" , peerIndex , orgIndex )
397
+ if _ , err := os .Stat (filepath .Join (w .Rootpath , peerName )); os .IsNotExist (err ) {
398
+ err := os .Mkdir (filepath .Join (w .Rootpath , peerName ), 0755 )
399
+ ExpectWithOffset (1 , err ).NotTo (HaveOccurred ())
400
+ }
401
+
402
+ helpers .CopyFile (
403
+ filepath .Join ("testdata" , fmt .Sprintf ("%s-core.yaml" , peerName )),
404
+ filepath .Join (w .Rootpath , peerName , "core.yaml" ),
405
+ )
406
+
407
+ peer := w .Components .Peer ()
408
+ peer .ConfigDir = filepath .Join (w .Rootpath , fmt .Sprintf ("peer%d.org%d.example.com" , peerIndex , orgIndex ))
409
+ peer .MSPConfigPath = filepath .Join (w .Rootpath , "crypto" , "peerOrganizations" , fmt .Sprintf ("org%d.example.com" , orgIndex ),
410
+ "users" , fmt .Sprintf ("Admin@org%d.example.com" , orgIndex ), "msp" )
411
+ peerProcess := ifrit .Invoke (peer .NodeStart (peerIndex ))
412
+ EventuallyWithOffset (2 , peerProcess .Ready ()).Should (BeClosed ())
413
+ ConsistentlyWithOffset (2 , peerProcess .Wait ()).ShouldNot (Receive ())
414
+ w .LocalProcess = append (w .LocalProcess , peerProcess )
415
+ }
416
+
274
417
func verifyMembership (w * world.World , d world.Deployment , expectedDiscoveredPeers []helpers.DiscoveredPeer ) {
275
418
sd := getDiscoveryService (1 , filepath .Join (w .Rootpath , "config_org1.yaml" ), w .Rootpath )
276
419
EventuallyWithOffset (1 , func () bool {
0 commit comments