Commit 17de263b authored by nirro's avatar nirro Committed by Nir Rozenbaum
Browse files

[FAB-10156] added e2e test for private data



tests dissemination of private data for orgranization added to
collection config.
the test also verifies that the added org don't have access to
old private data that was created before the org was added.

Change-Id: I1641d0a96d42779bfdce93d06b30b3853ec519d0
Signed-off-by: default avatarnirro <nirro@il.ibm.com>
parent d9855e92
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package marbles_private
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
// MarblesPrivateChaincode example Chaincode implementation
type MarblesPrivateChaincode struct {
}
type marble struct {
ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database
Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
}
type marblePrivateDetails struct {
ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database
Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around
Price int `json:"price"`
}
// Init initializes chaincode
// ===========================
func (t *MarblesPrivateChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
// Invoke - Our entry point for Invocations
// ========================================
func (t *MarblesPrivateChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
function, args := stub.GetFunctionAndParameters()
fmt.Println("invoke is running " + function)
// Handle different functions
switch function {
case "initMarble":
//create a new marble
return t.initMarble(stub, args)
case "readMarble":
//read a marble
return t.readMarble(stub, args)
case "readMarblePrivateDetails":
//read a marble private details
return t.readMarblePrivateDetails(stub, args)
case "delete":
//delete a marble
return t.delete(stub, args)
default:
//error
fmt.Println("invoke did not find func: " + function)
return shim.Error("Received unknown function invocation")
}
}
// ============================================================
// initMarble - create a new marble, store into chaincode state
// ============================================================
func (t *MarblesPrivateChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var err error
// 0-name 1-color 2-size 3-owner 4-price
// "asdf", "blue", "35", "bob", "99"
if len(args) != 5 {
return shim.Error("Incorrect number of arguments. Expecting 5")
}
// ==== Input sanitation ====
fmt.Println("- start init marble")
if len(args[0]) == 0 {
return shim.Error("1st argument must be a non-empty string")
}
if len(args[1]) == 0 {
return shim.Error("2nd argument must be a non-empty string")
}
if len(args[2]) == 0 {
return shim.Error("3rd argument must be a non-empty string")
}
if len(args[3]) == 0 {
return shim.Error("4th argument must be a non-empty string")
}
if len(args[4]) == 0 {
return shim.Error("5th argument must be a non-empty string")
}
marbleName := args[0]
color := strings.ToLower(args[1])
owner := strings.ToLower(args[3])
size, err := strconv.Atoi(args[2])
if err != nil {
return shim.Error("3rd argument must be a numeric string")
}
price, err := strconv.Atoi(args[4])
if err != nil {
return shim.Error("5th argument must be a numeric string")
}
// ==== Check if marble already exists ====
marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleName)
if err != nil {
return shim.Error("Failed to get marble: " + err.Error())
} else if marbleAsBytes != nil {
fmt.Println("This marble already exists: " + marbleName)
return shim.Error("This marble already exists: " + marbleName)
}
// ==== Create marble object and marshal to JSON ====
objectType := "marble"
marble := &marble{objectType, marbleName, color, size, owner}
marbleJSONasBytes, err := json.Marshal(marble)
if err != nil {
return shim.Error(err.Error())
}
//Alternatively, build the marble json string manually if you don't want to use struct marshalling
//marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}`
//marbleJSONasBytes := []byte(str)
// === Save marble to state ===
err = stub.PutPrivateData("collectionMarbles", marbleName, marbleJSONasBytes)
if err != nil {
return shim.Error(err.Error())
}
// ==== Save marble private details ====
objectType = "marblePrivateDetails"
marblePrivateDetails := &marblePrivateDetails{objectType, marbleName, price}
marblePrivateDetailsBytes, err := json.Marshal(marblePrivateDetails)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleName, marblePrivateDetailsBytes)
if err != nil {
return shim.Error(err.Error())
}
// ==== Marble saved. Return success ====
fmt.Println("- end init marble")
return shim.Success(nil)
}
// ===============================================
// readMarble - read a marble from chaincode state
// ===============================================
func (t *MarblesPrivateChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var name, jsonResp string
var err error
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
}
name = args[0]
valAsbytes, err := stub.GetPrivateData("collectionMarbles", name) //get the marble from chaincode state
if err != nil {
jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}"
return shim.Error(jsonResp)
} else if valAsbytes == nil {
jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}"
return shim.Error(jsonResp)
}
return shim.Success(valAsbytes)
}
// ===============================================
// readMarblereadMarblePrivateDetails - read a marble private details from chaincode state
// ===============================================
func (t *MarblesPrivateChaincode) readMarblePrivateDetails(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var name, jsonResp string
var err error
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
}
name = args[0]
valAsbytes, err := stub.GetPrivateData("collectionMarblePrivateDetails", name) //get the marble private details from chaincode state
if err != nil {
jsonResp = "{\"Error\":\"Failed to get private details for " + name + ": " + err.Error() + "\"}"
return shim.Error(jsonResp)
} else if valAsbytes == nil {
jsonResp = "{\"Error\":\"Marble private details does not exist: " + name + "\"}"
return shim.Error(jsonResp)
}
return shim.Success(valAsbytes)
}
// ==================================================
// delete - remove a marble key/value pair from state
// ==================================================
func (t *MarblesPrivateChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var jsonResp string
var marbleJSON marble
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
marbleName := args[0]
// to maintain the color~name index, we need to read the marble first and get its color
valAsbytes, err := stub.GetPrivateData("collectionMarbles", marbleName) //get the marble from chaincode state
if err != nil {
jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}"
return shim.Error(jsonResp)
} else if valAsbytes == nil {
jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}"
return shim.Error(jsonResp)
}
err = json.Unmarshal([]byte(valAsbytes), &marbleJSON)
if err != nil {
jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}"
return shim.Error(jsonResp)
}
err = stub.DelPrivateData("collectionMarbles", marbleName) //remove the marble from chaincode state
if err != nil {
return shim.Error("Failed to delete state:" + err.Error())
}
// maintain the index
indexName := "color~name"
colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name})
if err != nil {
return shim.Error(err.Error())
}
// Delete index entry to state.
err = stub.DelPrivateData("collectionMarbles", colorNameIndexKey)
if err != nil {
return shim.Error("Failed to delete state:" + err.Error())
}
// Delete private details of marble
err = stub.DelPrivateData("collectionMarblePrivateDetails", marbleName)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"fmt"
"os"
"github.com/hyperledger/fabric/integration/chaincode/marbles_private"
"github.com/hyperledger/fabric/core/chaincode/shim"
)
func main() {
err := shim.Start(&marbles_private.MarblesPrivateChaincode{})
if err != nil {
fmt.Fprintf(os.Stderr, "Exiting Simple chaincode: %s", err)
os.Exit(2)
}
}
/*
Copyright IBM Corp All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package e2e
import (
"encoding/json"
"github.com/hyperledger/fabric/integration/world"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestEndToEnd(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Private Data EndToEnd Suite ")
}
var components *world.Components
var _ = SynchronizedBeforeSuite(func() []byte {
components = &world.Components{}
components.Build()
payload, err := json.Marshal(components)
Expect(err).NotTo(HaveOccurred())
return payload
}, func(payload []byte) {
err := json.Unmarshal(payload, &components)
Expect(err).NotTo(HaveOccurred())
})
var _ = SynchronizedAfterSuite(func() {
}, func() {
components.Cleanup()
})
/*
Copyright IBM Corp All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package e2e
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/hyperledger/fabric/integration/helpers"
"github.com/hyperledger/fabric/integration/runner"
"github.com/hyperledger/fabric/integration/world"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/fsouza/go-dockerclient"
)
var _ = Describe("PrivateData-EndToEnd", func() {
var (
testDir string
w *world.World
d world.Deployment
expectedDiscoveredPeers []helpers.DiscoveredPeer
)
Describe("collection config is modified", func() {
BeforeEach(func() {
var err error
testDir, err = ioutil.TempDir("", "e2e-pvtdata")
Expect(err).NotTo(HaveOccurred())
w = world.GenerateBasicConfig("solo", 1, 3, testDir, components)
d = world.Deployment{
Channel: "testchannel",
Chaincode: world.Chaincode{
Name: "marblesp",
Version: "1.0",
Path: "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd",
ExecPath: os.Getenv("PATH"),
CollectionsConfigPath: filepath.Join("testdata", "collection_configs", "collections_config1.json"),
},
InitArgs: `{"Args":["init"]}`,
Policy: `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`,
Orderer: "127.0.0.1:7050",
}
w.SetupWorld(d)
By("setting up all anchor peers")
setAnchorsPeerForAllOrgs(1, 3, d, w.Rootpath)
By("verify membership was built using discovery service")
expectedDiscoveredPeers = []helpers.DiscoveredPeer{
{"Org1MSP", 0, "0.0.0.0:7051", "", []string{}},
{"Org2MSP", 0, "0.0.0.0:8051", "", []string{}},
{"Org3MSP", 0, "0.0.0.0:9051", "", []string{}},
}
verifyMembership(w, d, expectedDiscoveredPeers)
By("invoking initMarble function of the chaincode")
adminPeer := getPeer(0, 1, w.Rootpath)
adminRunner := adminPeer.InvokeChaincode(d.Chaincode.Name, d.Channel, `{"Args":["initMarble","marble1","blue","35","tom","99"]}`, d.Orderer)
err = helpers.Execute(adminRunner)
Expect(err).NotTo(HaveOccurred())
Expect(adminRunner.Err()).To(gbytes.Say("Chaincode invoke successful."))
By("check that the access of different peers is as defined in collection config")
peerList := []*runner.Peer{getPeer(0, 1, w.Rootpath), getPeer(0, 2, w.Rootpath)}
verifyAccess(d.Chaincode.Name, d.Channel, `{"Args":["readMarble","marble1"]}`, peerList, `{"docType":"marble","name":"marble1","color":"blue","size":35,"owner":"tom"}`)
peerList = []*runner.Peer{getPeer(0, 2, w.Rootpath), getPeer(0, 3, w.Rootpath)}
verifyAccess(d.Chaincode.Name, d.Channel, `{"Args":["readMarblePrivateDetails","marble1"]}`, peerList, `{"docType":"marblePrivateDetails","name":"marble1","price":99}`)
By("querying collectionMarblePrivateDetails by peer0.org1, shouldn't have access")
adminPeer = getPeer(0, 1, w.Rootpath)
verifyAccessFailed(d.Chaincode.Name, d.Channel, `{"Args":["readMarblePrivateDetails","marble1"]}`, adminPeer, "Private data matching public hash version is not available")
By("querying collectionMarbles by peer0.org3, shouldn't have access")
adminPeer = getPeer(0, 3, w.Rootpath)
verifyAccessFailed(d.Chaincode.Name, d.Channel, `{"Args":["readMarble","marble1"]}`, adminPeer, "Failed to get state for marble1")
By("installing chaincode version 2.0 on all peers")
installChaincodeOnAllPeers(1, 3, d.Chaincode.Name, "2.0", "github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd", testDir)
})
AfterEach(func() {
if w != nil {
w.Close(d)
}
// Stop the running chaincode containers
filters := map[string][]string{}
filters["name"] = []string{
fmt.Sprintf("%s-1.0", d.Chaincode.Name),
fmt.Sprintf("%s-2.0", d.Chaincode.Name),
fmt.Sprintf("%s-3.0", d.Chaincode.Name),
}
allContainers, _ := w.DockerClient.ListContainers(docker.ListContainersOptions{
Filters: filters,
})
for _, container := range allContainers {
w.DockerClient.RemoveContainer(docker.RemoveContainerOptions{
ID: container.ID,
Force: true,
})
}
os.RemoveAll(testDir)
})
It("verifies access to private data after an org is added to collection config", func() {
By("upgrading chaincode in order to update collections config")
adminPeer := getPeer(0, 1, testDir)
adminPeer.UpgradeChaincode(d.Chaincode.Name, "2.0", d.Orderer, d.Channel, `{"Args":["init"]}`, `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`, filepath.Join("testdata", "collection_configs", "collections_config2.json"))
By("invoking initMarble function of the chaincode")
adminPeer = getPeer(0, 2, testDir)
adminRunner := adminPeer.InvokeChaincode(d.Chaincode.Name, d.Channel, `{"Args":["initMarble","marble2","yellow","53","jerry","22"]}`, d.Orderer)
err := helpers.Execute(adminRunner)
Expect(err).NotTo(HaveOccurred())
Expect(adminRunner.Err()).To(gbytes.Say("Chaincode invoke successful."))
By("check that the access of different peers is as defined in collection config")
peerList := []*runner.Peer{getPeer(0, 1, testDir), getPeer(0, 2, testDir), getPeer(0, 3, testDir)}
verifyAccess(d.Chaincode.Name, d.Channel, `{"Args":["readMarble","marble2"]}`, peerList, `{"docType":"marble","name":"marble2","color":"yellow","size":53,"owner":"jerry"}`)
peerList = []*runner.Peer{getPeer(0, 2, testDir), getPeer(0, 3, testDir)}
verifyAccess(d.Chaincode.Name, d.Channel, `{"Args":["readMarblePrivateDetails","marble2"]}`, peerList, `{"docType":"marblePrivateDetails","name":"marble2","price":22}`)
By("querying collectionMarblePrivateDetails by peer0.org1, shouldn't have access")
adminPeer = getPeer(0, 1, testDir)
verifyAccessFailed(d.Chaincode.Name, d.Channel, `{"Args":["readMarblePrivateDetails","marble2"]}`, adminPeer, "Private data matching public hash version is not available")
By("querying collectionMarbles by peer0.org3, make sure it doesn't have access to marble1 that was created before adding peer0.org3 to the config")
adminPeer = getPeer(0, 3, testDir)
verifyAccessFailed(d.Chaincode.Name, d.Channel, `{"Args":["readMarble","marble1"]}`, adminPeer, "Failed to get state for marble1")
})
})
})
func getPeer(peer int, org int, testDir string) *runner.Peer {
adminPeer := components.Peer()
adminPeer.LogLevel = "debug"
adminPeer.ConfigDir = filepath.Join(testDir, fmt.Sprintf("peer%d.org%d.example.com", peer, org))
adminPeer.MSPConfigPath = filepath.Join(testDir, "crypto", "peerOrganizations", fmt.Sprintf("org%d.example.com", org), "users", fmt.Sprintf("Admin@org%d.example.com", org), "msp")
return adminPeer
}
func setAnchorsPeerForAllOrgs(numPeers int, numOrgs int, d world.Deployment, testDir string) {
for orgIndex := 1; orgIndex <= numOrgs; orgIndex++ {
for peerIndex := 0; peerIndex < numPeers; peerIndex++ {
adminPeer := getPeer(peerIndex, orgIndex, testDir)
adminRunner := adminPeer.UpdateChannel(filepath.Join(testDir, fmt.Sprintf("Org%d_anchors_update_tx.pb", orgIndex)), d.Channel, d.Orderer)
err := helpers.Execute(adminRunner)
Expect(err).NotTo(HaveOccurred())
Expect(adminRunner.Err()).To(gbytes.Say("Successfully submitted channel update"))
}
}
}
func installChaincodeOnAllPeers(numPeers int, numOrgs int, ccname string, ccversion string, ccpath string, testDir string) {
for orgIndex := 1; orgIndex <= numOrgs; orgIndex++ {
for peerIndex := 0; peerIndex < numPeers; peerIndex++ {
adminPeer := getPeer(peerIndex, orgIndex, testDir)
adminPeer.InstallChaincode(ccname, ccversion, ccpath)
}
}
}
func verifyAccess(ccname string, channel string, args string, peers []*runner.Peer, expected string) {
for _, peer := range peers {
EventuallyWithOffset(1, func() (*gbytes.Buffer, error) {
adminRunner := peer.QueryChaincode(ccname, channel, args)
err := helpers.Execute(adminRunner)
return adminRunner.Buffer(), err
}, time.Minute).Should(gbytes.Say(expected)) // this calls will also verify error didn't occur
}
}
func verifyAccessFailed(ccname string, channel string, args string, peer *runner.Peer, expectedFailureMessage string) {
EventuallyWithOffset(1, func() *gbytes.Buffer {
adminRunner := peer.QueryChaincode(ccname, channel, args)
err := helpers.Execute(adminRunner)
Expect(err).To(HaveOccurred())
return adminRunner.Err()
}, time.Minute).Should(gbytes.Say(expectedFailureMessage))
}
func verifyMembership(w *world.World, d world.Deployment, expectedDiscoveredPeers []helpers.DiscoveredPeer) {
sd := getDiscoveryService(1, filepath.Join(w.Rootpath, "config_org1.yaml"), w.Rootpath)
EventuallyWithOffset(1, func() bool {
_, result := runner.VerifyAllPeersDiscovered(sd, expectedDiscoveredPeers, d.Channel, "127.0.0.1:7051")
return result
}, time.Minute).Should(BeTrue())
}
func getDiscoveryService(org int, configFilePath string, testDir string) *runner.DiscoveryService {
userCert := filepath.Join(testDir, "crypto", "peerOrganizations", fmt.Sprintf("org%d.example.com", org), "users",
fmt.Sprintf("User1@org%d.example.com", org), "msp", "signcerts", fmt.Sprintf("User1@org%d.example.com-cert.pem", org))
userKeyDir := filepath.Join(testDir, "crypto", "peerOrganizations", fmt.Sprintf("org%d.example.com", org), "users",
fmt.Sprintf("User1@org%d.example.com", org), "msp", "keystore")
return runner.SetupDiscoveryService(components.DiscoveryService(), org, configFilePath, userCert, userKeyDir)
}
[
{
"name": "collectionMarbles",
"policy": "OR('Org1MSP.member', 'Org2MSP.member')",
"requiredPeerCount": 1,
"maxPeerCount": 2,
"blockToLive":1000000
},
{
"name": "collectionMarblePrivateDetails",
"policy": "OR('Org2MSP.member', 'Org3MSP.member')",
"requiredPeerCount": 1,
"maxPeerCount": 2,
"blockToLive":1000000
}
]
[
{
"name": "collectionMarbles",
"policy": "OR('Org1MSP.member', 'Org2MSP.member', 'Org3MSP.member')",
"requiredPeerCount": 1,
"maxPeerCount": 2,
"blockToLive":1000000
},
{
"name": "collectionMarblePrivateDetails",
"policy": "OR('Org2MSP.member', 'Org3MSP.member')",
"requiredPeerCount": 1,
"maxPeerCount": 2,
"blockToLive":1000000
}
]
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
---
General:
LedgerType: file
ListenAddress: 127.0.0.1
ListenPort: 7050
TLS:
Enabled: false
PrivateKey: tls/server.key
Certificate: tls/server.crt
RootCAs:
- tls/ca.crt