/*
Copyright IBM Corp All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package discovery

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"syscall"
	"time"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/hyperledger/fabric-protos-go-apiv2/common"
	"github.com/hyperledger/fabric-protos-go-apiv2/discovery"
	pm "github.com/hyperledger/fabric-protos-go-apiv2/msp"
	"github.com/hyperledger/fabric/common/policydsl"
	"github.com/hyperledger/fabric/integration/channelparticipation"
	"github.com/hyperledger/fabric/integration/nwo"
	"github.com/hyperledger/fabric/integration/nwo/commands"
	. "github.com/hyperledger/fabric/internal/test"
	"github.com/hyperledger/fabric/msp"
	"github.com/hyperledger/fabric/protoutil"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/tedsuo/ifrit"
	ginkgomon "github.com/tedsuo/ifrit/ginkgomon_v2"
	"google.golang.org/protobuf/proto"
)

var _ = Describe("DiscoveryService", func() {
	var (
		testDir        string
		client         *docker.Client
		config         *nwo.Config
		network        *nwo.Network
		ordererRunner  *ginkgomon.Runner
		ordererProcess ifrit.Process
		peerProcesses  []ifrit.Process
		orderer        *nwo.Orderer
		org1Peer0      *nwo.Peer
		org2Peer0      *nwo.Peer
		org3Peer0      *nwo.Peer
	)

	BeforeEach(func() {
		var err error
		testDir, err = os.MkdirTemp("", "e2e-sd")
		Expect(err).NotTo(HaveOccurred())

		client, err = docker.NewClientFromEnv()
		Expect(err).NotTo(HaveOccurred())

		config = nwo.BasicEtcdRaft()
		Expect(config.Peers).To(HaveLen(2))
	})

	AfterEach(func() {
		if ordererProcess != nil {
			ordererProcess.Signal(syscall.SIGTERM)
			Eventually(ordererProcess.Wait(), network.EventuallyTimeout).Should(Receive())
		}

		for _, process := range peerProcesses {
			process.Signal(syscall.SIGTERM)
			Eventually(process.Wait(), network.EventuallyTimeout).Should(Receive())
		}
		if network != nil {
			network.Cleanup()
		}
		os.RemoveAll(testDir)
	})

	Describe("basic etcdraft network without anchor peers", func() {
		BeforeEach(func() {
			By("generating a network with no anchor peers defined")
			// disable all anchor peers
			for _, p := range config.Peers {
				for _, pc := range p.Channels {
					pc.Anchor = false
				}
			}

			network = nwo.New(config, testDir, client, StartPort(), components)
			network.GenerateConfigTree()
			network.Bootstrap()

			By("asserting there are no anchor peers in the genesis block")
			assertAnchorPeers(network, false, "testchannel", "Org1", "Org2")

			// Start all the fabric processes
			var peerProcess ifrit.Process
			ordererRunner, ordererProcess, peerProcess = network.StartSingleOrdererNetwork("orderer")
			peerProcesses = append(peerProcesses, peerProcess)

			orderer = network.Orderer("orderer")
			channelparticipation.JoinOrdererJoinPeersAppChannel(network, "testchannel", orderer, ordererRunner)

			org1Peer0 = network.Peer("Org1", "peer0")
			org2Peer0 = network.Peer("Org2", "peer0")
		})
		It("discovers network configuration even without anchor peers present", func() {
			chaincodeWhenNoAnchorPeers := nwo.Chaincode{
				Name:            "mycc",
				Version:         "0.0",
				Path:            components.Build("github.com/hyperledger/fabric/integration/chaincode/simple/cmd"),
				Lang:            "binary",
				PackageFile:     filepath.Join(testDir, "simplecc.tar.gz"),
				Ctor:            `{"Args":["init","a","100","b","200"]}`,
				SignaturePolicy: `OR ('Org1MSP.member')`,
				Sequence:        "1",
				InitRequired:    true,
				Label:           "my_prebuilt_chaincode",
			}

			By("enabling capabilities V2_5")
			nwo.EnableCapabilities(network, "testchannel", "Application", "V2_5", orderer, org1Peer0, org2Peer0)

			By("Deploying chaincode before anchor peers are defined in the channel")
			nwo.DeployChaincode(network, "testchannel", orderer, chaincodeWhenNoAnchorPeers, org1Peer0, org2Peer0)

			endorsersForChaincodeBeforeAnchorPeersExist := commands.Endorsers{
				UserCert:  network.PeerUserCert(org1Peer0, "User1"),
				UserKey:   network.PeerUserKey(org1Peer0, "User1"),
				MSPID:     network.Organization(org1Peer0.Organization).MSPID,
				Server:    network.PeerAddress(org1Peer0, nwo.ListenPort),
				Channel:   "testchannel",
				Chaincode: chaincodeWhenNoAnchorPeers.Name,
			}
			discoveryQuery := discoverEndorsers(network, endorsersForChaincodeBeforeAnchorPeersExist)
			Eventually(discoveryQuery, network.EventuallyTimeout).Should(BeEquivalentTo(
				[]ChaincodeEndorsers{
					{
						Chaincode: chaincodeWhenNoAnchorPeers.Name,
						EndorsersByGroups: map[string][]nwo.DiscoveredPeer{
							"G0": {network.DiscoveredPeer(org1Peer0)},
						},
						Layouts: []*discovery.Layout{
							{
								QuantitiesByGroup: map[string]uint32{"G0": 1},
							},
						},
					},
				},
			))
		})

		It("discovers all peers after explicitly updating anchor peers", func() {
			By("discovering peers without anchors yields partitioned information")
			Eventually(nwo.DiscoverPeers(network, org1Peer0, "User1", "testchannel"), network.EventuallyTimeout).Should(ConsistOf(
				network.DiscoveredPeer(org1Peer0, "_lifecycle"),
			))
			Eventually(nwo.DiscoverPeers(network, org2Peer0, "User1", "testchannel"), network.EventuallyTimeout).Should(ConsistOf(
				network.DiscoveredPeer(org2Peer0, "_lifecycle"),
			))

			By("explicitly setting anchor peers on both organizations")
			network.UpdateOrgAnchorPeers(orderer, "testchannel", "Org1", network.PeersInOrg("Org1"))
			network.UpdateOrgAnchorPeers(orderer, "testchannel", "Org2", network.PeersInOrg("Org2"))

			config := nwo.GetConfig(network, org1Peer0, orderer, "testchannel")
			configGroups := config.GetChannelGroup().GetGroups()["Application"]
			Expect(configGroups.GetGroups()["Org1"].GetValues()["AnchorPeers"].GetValue()).ToNot(BeEmpty())
			Expect(configGroups.GetGroups()["Org2"].GetValues()["AnchorPeers"].GetValue()).ToNot(BeEmpty())

			By("discovering peers with anchors yields complete information")
			Eventually(nwo.DiscoverPeers(network, org1Peer0, "User1", "testchannel"), network.EventuallyTimeout).Should(ConsistOf(
				network.DiscoveredPeer(org1Peer0, "_lifecycle"),
				network.DiscoveredPeer(org2Peer0, "_lifecycle"),
			))
			Eventually(nwo.DiscoverPeers(network, org2Peer0, "User1", "testchannel"), network.EventuallyTimeout).Should(ConsistOf(
				network.DiscoveredPeer(org1Peer0, "_lifecycle"),
				network.DiscoveredPeer(org2Peer0, "_lifecycle"),
			))
		})
	})

	Describe("basic etcdraft network with anchor peers", func() {
		BeforeEach(func() {
			By("generating a network with anchor peers defined and adding a 3rd org")
			// add org3 with one peer (to generate cryptogen and configtx files)
			config.Organizations = append(config.Organizations, &nwo.Organization{
				Name:          "Org3",
				MSPID:         "Org3MSP",
				Domain:        "org3.example.com",
				EnableNodeOUs: true,
				Users:         2,
				CA:            &nwo.CA{Hostname: "ca"},
			})
			config.Profiles[0].Organizations = append(config.Profiles[0].Organizations, "Org3")
			config.Peers = append(config.Peers,
				&nwo.Peer{
					Name:         "peer0",
					Organization: "Org3",
					Channels: []*nwo.PeerChannel{
						{Name: "testchannel", Anchor: true},
					},
				},
			)

			network = nwo.New(config, testDir, client, StartPort(), components)
			network.GenerateConfigTree()
			network.Bootstrap()

			// initially remove Org3 peer0 from the network and later add it back to test joinbysnapshot
			peers := []*nwo.Peer{}
			for _, p := range network.Peers {
				if p.Organization != "Org3" {
					peers = append(peers, p)
				}
			}
			network.Peers = peers

			By("asserting the genesis block has anchor peers defined")
			assertAnchorPeers(network, true, "testchannel", "Org1", "Org2", "Org3")

			// Start all the fabric processes
			var peerProcess ifrit.Process
			ordererRunner, ordererProcess, peerProcess = network.StartSingleOrdererNetwork("orderer")
			peerProcesses = append(peerProcesses, peerProcess)

			orderer = network.Orderer("orderer")
			channelparticipation.JoinOrdererJoinPeersAppChannel(network, "testchannel", orderer, ordererRunner)

			org1Peer0 = network.Peer("Org1", "peer0")
			org2Peer0 = network.Peer("Org2", "peer0")
		})

		It("discovers network configuration, endorsers, and peer membership", func() {
			//
			// bootstrapping a peer from snapshot
			//
			By("generating a snapshot at current block number on org1Peer0")
			blockNum := nwo.GetLedgerHeight(network, org1Peer0, "testchannel") - 1
			submitSnapshotRequest(network, "testchannel", 0, org1Peer0, "Snapshot request submitted successfully")

			By("verifying snapshot completed on org1Peer0")
			verifyNoPendingSnapshotRequest(network, org1Peer0, "testchannel")
			snapshotDir := filepath.Join(network.PeerDir(org1Peer0), "filesystem", "snapshots", "completed", "testchannel", strconv.Itoa(blockNum))

			By("adding peer org3Peer0 to the network")
			org3Peer0 = &nwo.Peer{
				Name:         "peer0",
				Organization: "Org3",
				Channels: []*nwo.PeerChannel{
					{Name: "testchannel", Anchor: true},
				},
			}
			network.Peers = append(network.Peers, org3Peer0)

			By("starting peer org3Peer0")
			peerRunner := network.PeerRunner(org3Peer0)
			process := ifrit.Invoke(peerRunner)
			Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
			peerProcesses = append(peerProcesses, process)

			By("joining peer org3Peer0 to channel by a snapshot")
			joinBySnapshot(network, orderer, org3Peer0, "testchannel", snapshotDir)

			//
			// retrieving configuration and validating membership
			//

			By("retrieving the configuration from org1Peer0")
			discoveredConfig := discoverConfiguration(network, org1Peer0)

			By("retrieving the configuration from org3Peer0")
			discoveredConfig2 := discoverConfiguration(network, org3Peer0)

			By("comparing configuration from org1Peer0 and org3Peer0")
			Expect(discoveredConfig).To(ProtoEqual(discoveredConfig2))

			By("validating the membership data")
			Expect(discoveredConfig.Msps).To(HaveLen(len(network.Organizations)))
			for _, o := range network.Orderers {
				org := network.Organization(o.Organization)
				mspConfig, err := msp.GetVerifyingMspConfig(network.OrdererOrgMSPDir(org), org.MSPID, "bccsp")
				Expect(err).NotTo(HaveOccurred())
				Expect(discoveredConfig.Msps[org.MSPID]).To(ProtoEqual(unmarshalFabricMSPConfig(mspConfig)))
			}
			for _, p := range network.Peers {
				org := network.Organization(p.Organization)
				mspConfig, err := msp.GetVerifyingMspConfig(network.PeerOrgMSPDir(org), org.MSPID, "bccsp")
				Expect(err).NotTo(HaveOccurred())
				Expect(discoveredConfig.Msps[org.MSPID]).To(ProtoEqual(unmarshalFabricMSPConfig(mspConfig)))
			}

			By("validating the orderers")
			Expect(discoveredConfig.Orderers).To(HaveLen(len(network.Orderers)))
		external:
			for _, orderer := range network.Orderers {
				ordererMSPID := network.Organization(orderer.Organization).MSPID
				for _, endpoint := range discoveredConfig.Orderers[ordererMSPID].Endpoint {
					if proto.Equal(endpoint,
						&discovery.Endpoint{Host: "127.0.0.1", Port: uint32(network.OrdererPort(orderer, nwo.ListenPort))},
					) {
						continue external
					}
				}
				Fail("no match orderer host")
			}

			//
			// discovering peers and endorsers
			//

			By("enabling V2_0 application capabilities on the channel")
			nwo.EnableCapabilities(network, "testchannel", "Application", "V2_0", orderer, org1Peer0, org2Peer0, org3Peer0)

			By("discovering peers before deploying any user chaincodes")
			Eventually(nwo.DiscoverPeers(network, org1Peer0, "User1", "testchannel"), network.EventuallyTimeout).Should(ConsistOf(
				network.DiscoveredPeer(org1Peer0, "_lifecycle"),
				network.DiscoveredPeer(org2Peer0, "_lifecycle"),
				network.DiscoveredPeer(org3Peer0, "_lifecycle"),
			))

			By("discovering endorsers for _lifecycle system chaincode")
			endorsers := commands.Endorsers{
				UserCert:  network.PeerUserCert(org1Peer0, "User1"),
				UserKey:   network.PeerUserKey(org1Peer0, "User1"),
				MSPID:     network.Organization(org1Peer0.Organization).MSPID,
				Server:    network.PeerAddress(org1Peer0, nwo.ListenPort),
				Channel:   "testchannel",
				Chaincode: "_lifecycle",
			}
			de := discoverEndorsers(network, endorsers)
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org3Peer0)},
			))
			discovered := de()
			Expect(discovered).To(HaveLen(1))
			Expect(discovered[0].Layouts).To(HaveLen(3))
			Expect(discovered[0].Layouts[0].QuantitiesByGroup).To(ConsistOf(uint32(1), uint32(1)))
			Expect(discovered[0].Layouts[1].QuantitiesByGroup).To(ConsistOf(uint32(1), uint32(1)))
			Expect(discovered[0].Layouts[2].QuantitiesByGroup).To(ConsistOf(uint32(1), uint32(1)))

			By("discovering endorsers when missing chaincode")
			endorsers = commands.Endorsers{
				UserCert:  network.PeerUserCert(org1Peer0, "User1"),
				UserKey:   network.PeerUserKey(org1Peer0, "User1"),
				MSPID:     network.Organization(org1Peer0.Organization).MSPID,
				Server:    network.PeerAddress(org1Peer0, nwo.ListenPort),
				Channel:   "testchannel",
				Chaincode: "mycc-lifecycle",
			}
			sess, err := network.Discover(endorsers)
			Expect(err).NotTo(HaveOccurred())
			Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(1))
			Expect(sess.Err).To(gbytes.Say(`failed constructing descriptor for chaincodes:{name:"mycc-lifecycle"`))

			By("deploying chaincode using org1 and org2")
			chaincodePath := components.Build("github.com/hyperledger/fabric/integration/chaincode/simple/cmd")
			chaincode := nwo.Chaincode{
				Name:                "mycc-lifecycle",
				Version:             "1.0",
				Lang:                "binary",
				PackageFile:         filepath.Join(testDir, "simplecc.tar.gz"),
				Path:                chaincodePath,
				Ctor:                `{"Args":["init","a","100","b","200"]}`,
				ChannelConfigPolicy: "/Channel/Application/Endorsement",
				Sequence:            "1",
				InitRequired:        true,
				Label:               "my_prebuilt_chaincode",
			}

			By("packaging chaincode")
			nwo.PackageChaincodeBinary(chaincode)

			By("installing chaincode to org1.peer0 and org2.peer0")
			nwo.InstallChaincode(network, chaincode, org1Peer0, org2Peer0)

			By("approving chaincode definition for org1 and org2")
			for _, org := range []string{"Org1", "Org2"} {
				nwo.ApproveChaincodeForMyOrg(network, "testchannel", orderer, chaincode, network.PeersInOrg(org)...)
			}

			By("committing chaincode definition using org1.peer0 and org2.peer0")
			nwo.CommitChaincode(network, "testchannel", orderer, chaincode, org1Peer0, org1Peer0, org2Peer0)
			nwo.InitChaincode(network, "testchannel", orderer, chaincode, org1Peer0, org2Peer0)

			By("discovering endorsers for chaincode that has been installed to some orgs")
			de = discoverEndorsers(network, endorsers)
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
			))
			discovered = de()
			Expect(discovered).To(HaveLen(1))
			Expect(discovered[0].Layouts).To(HaveLen(1))
			Expect(discovered[0].Layouts[0].QuantitiesByGroup).To(ConsistOf(uint32(1), uint32(1)))

			By("installing chaincode to all orgs")
			nwo.InstallChaincode(network, chaincode, org3Peer0)

			By("discovering endorsers for chaincode that has been installed to all orgs but not yet approved by org3")
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
			))

			By("ensuring peers are only discoverable that have the chaincode installed and approved")
			dp := nwo.DiscoverPeers(network, org1Peer0, "User1", "testchannel")
			Eventually(peersWithChaincode(dp, "mycc-lifecycle"), network.EventuallyTimeout).Should(HaveLen(2))
			peersWithCC := peersWithChaincode(dp, "mycc-lifecycle")()
			Expect(peersWithCC).To(ConsistOf(
				network.DiscoveredPeer(org1Peer0, "mycc-lifecycle", "_lifecycle"),
				network.DiscoveredPeer(org2Peer0, "mycc-lifecycle", "_lifecycle"),
			))

			By("approving chaincode definition for org3")
			nwo.ApproveChaincodeForMyOrg(network, "testchannel", orderer, chaincode, network.PeersInOrg("Org3")...)

			By("discovering endorsers for chaincode that has been installed and approved by all orgs")
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org3Peer0)},
			))

			By("discovering peers for chaincode that has been installed and approved by all orgs")
			dp = nwo.DiscoverPeers(network, org1Peer0, "User1", "testchannel")
			Eventually(peersWithChaincode(dp, "mycc-lifecycle"), network.EventuallyTimeout).Should(HaveLen(3))
			peersWithCC = peersWithChaincode(dp, "mycc-lifecycle")()
			Expect(peersWithCC).To(ConsistOf(
				network.DiscoveredPeer(org1Peer0, "mycc-lifecycle", "_lifecycle"),
				network.DiscoveredPeer(org2Peer0, "mycc-lifecycle", "_lifecycle"),
				network.DiscoveredPeer(org3Peer0, "mycc-lifecycle", "_lifecycle"),
			))

			By("updating the chaincode definition to sequence 2 to add a collections config")
			chaincode.Sequence = "2"
			chaincode.CollectionsConfig = filepath.Join("testdata", "collections_config_org1_org2.json")
			for _, org := range []string{"Org1", "Org2"} {
				nwo.ApproveChaincodeForMyOrg(network, "testchannel", orderer, chaincode, network.PeersInOrg(org)...)
			}

			By("committing the new chaincode definition using org1 and org2")
			nwo.CheckCommitReadinessUntilReady(network, "testchannel", chaincode, []*nwo.Organization{network.Organization("Org1"), network.Organization("Org2")}, org1Peer0, org2Peer0, org3Peer0)
			nwo.CommitChaincode(network, "testchannel", orderer, chaincode, org1Peer0, org1Peer0, org2Peer0)

			By("discovering endorsers for sequence 2 that has only been approved by org1 and org2")
			de = discoverEndorsers(network, endorsers)
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
			))

			By("approving the chaincode definition at sequence 2 by org3")
			maxLedgerHeight := nwo.GetMaxLedgerHeight(network, "testchannel", org1Peer0, org2Peer0, org3Peer0)
			nwo.ApproveChaincodeForMyOrg(network, "testchannel", orderer, chaincode, network.PeersInOrg("Org3")...)
			nwo.WaitUntilEqualLedgerHeight(network, "testchannel", maxLedgerHeight+1, org1Peer0, org2Peer0, org3Peer0)

			By("discovering endorsers for sequence 2 that has been approved by all orgs")
			de = discoverEndorsers(network, endorsers)
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org3Peer0)},
			))

			By("discovering endorsers for chaincode with a private collection")
			endorsers.Collection = "mycc-lifecycle:collectionMarbles"
			de = discoverEndorsers(network, endorsers)
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org1Peer0)},
				[]nwo.DiscoveredPeer{network.DiscoveredPeer(org2Peer0)},
			))
			discovered = de()
			Expect(discovered).To(HaveLen(1))
			Expect(discovered[0].Layouts).To(HaveLen(1))
			Expect(discovered[0].Layouts[0].QuantitiesByGroup).To(ConsistOf(uint32(1), uint32(1)))

			By("changing the channel policy")
			currentConfig := nwo.GetConfig(network, org3Peer0, orderer, "testchannel")
			updatedConfig := proto.Clone(currentConfig).(*common.Config)
			updatedConfig.ChannelGroup.Groups["Application"].Groups["Org3"].Policies["Writers"].Policy.Value = protoutil.MarshalOrPanic(policydsl.SignedByMspAdmin("Org3MSP"))
			nwo.UpdateConfig(network, orderer, "testchannel", currentConfig, updatedConfig, true, org3Peer0, nil)

			By("trying to discover endorsers as an org3 admin")
			endorsers = commands.Endorsers{
				UserCert:  network.PeerUserCert(org3Peer0, "Admin"),
				UserKey:   network.PeerUserKey(org3Peer0, "Admin"),
				MSPID:     network.Organization(org3Peer0.Organization).MSPID,
				Server:    network.PeerAddress(org3Peer0, nwo.ListenPort),
				Channel:   "testchannel",
				Chaincode: "mycc-lifecycle",
			}

			de = discoverEndorsers(network, endorsers)
			Eventually(endorsersByGroups(de), network.EventuallyTimeout).Should(ConsistOf(
				ConsistOf(network.DiscoveredPeer(org1Peer0)),
				ConsistOf(network.DiscoveredPeer(org2Peer0)),
				ConsistOf(network.DiscoveredPeer(org3Peer0)),
			))

			By("trying to discover endorsers as an org3 member")
			endorsers = commands.Endorsers{
				UserCert:  network.PeerUserCert(org3Peer0, "User1"),
				UserKey:   network.PeerUserKey(org3Peer0, "User1"),
				MSPID:     network.Organization(org3Peer0.Organization).MSPID,
				Server:    network.PeerAddress(org3Peer0, nwo.ListenPort),
				Channel:   "testchannel",
				Chaincode: "mycc-lifecycle",
			}
			sess, err = network.Discover(endorsers)
			Expect(err).NotTo(HaveOccurred())
			Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(1))
			Expect(sess.Err).To(gbytes.Say(`access denied`))
		})
	})
})

func assertAnchorPeers(network *nwo.Network, exist bool, channelID string, orgs ...string) {
	// get the genesis block
	configBlock := nwo.UnmarshalBlockFromFile(network.OutputBlockPath(channelID))
	envelope, err := protoutil.GetEnvelopeFromBlock(configBlock.Data.Data[0])
	Expect(err).NotTo(HaveOccurred())

	// unmarshal the payload bytes
	payload, err := protoutil.UnmarshalPayload(envelope.Payload)
	Expect(err).NotTo(HaveOccurred())

	// unmarshal the config envelope bytes
	configEnv := &common.ConfigEnvelope{}
	err = proto.Unmarshal(payload.Data, configEnv)
	Expect(err).NotTo(HaveOccurred())

	config := configEnv.GetConfig()
	configGroups := config.GetChannelGroup().GetGroups()["Application"]

	for _, org := range orgs {
		if exist {
			Expect(configGroups.GetGroups()[org].GetValues()["AnchorPeers"].GetValue()).ToNot(BeEmpty())
		} else {
			Expect(configGroups.GetGroups()[org].GetValues()["AnchorPeers"].GetValue()).To(BeEmpty())
		}
	}
}

type ChaincodeEndorsers struct {
	Chaincode         string
	EndorsersByGroups map[string][]nwo.DiscoveredPeer
	Layouts           []*discovery.Layout
}

func discoverEndorsers(n *nwo.Network, command commands.Endorsers) func() []ChaincodeEndorsers {
	return func() []ChaincodeEndorsers {
		sess, err := n.Discover(command)
		Expect(err).NotTo(HaveOccurred())
		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
		if sess.ExitCode() != 0 {
			return nil
		}

		discovered := []ChaincodeEndorsers{}
		err = json.Unmarshal(sess.Out.Contents(), &discovered)
		Expect(err).NotTo(HaveOccurred())
		return discovered
	}
}

func endorsersByGroups(discover func() []ChaincodeEndorsers) func() map[string][]nwo.DiscoveredPeer {
	return func() map[string][]nwo.DiscoveredPeer {
		discovered := discover()
		if len(discovered) == 1 {
			return discovered[0].EndorsersByGroups
		}
		return map[string][]nwo.DiscoveredPeer{}
	}
}

func peersWithChaincode(discover func() []nwo.DiscoveredPeer, ccName string) func() []nwo.DiscoveredPeer {
	return func() []nwo.DiscoveredPeer {
		peers := []nwo.DiscoveredPeer{}
		for _, p := range discover() {
			for _, cc := range p.Chaincodes {
				if cc == ccName {
					peers = append(peers, p)
				}
			}
		}
		return peers
	}
}

func unmarshalFabricMSPConfig(c *pm.MSPConfig) *pm.FabricMSPConfig {
	fabricConfig := &pm.FabricMSPConfig{}
	err := proto.Unmarshal(c.Config, fabricConfig)
	Expect(err).NotTo(HaveOccurred())
	return fabricConfig
}

func discoverConfiguration(n *nwo.Network, peer *nwo.Peer) *discovery.ConfigResult {
	config := commands.Config{
		UserCert: n.PeerUserCert(peer, "User1"),
		UserKey:  n.PeerUserKey(peer, "User1"),
		MSPID:    n.Organization(peer.Organization).MSPID,
		Server:   n.PeerAddress(peer, nwo.ListenPort),
		Channel:  "testchannel",
	}
	sess, err := n.Discover(config)
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))

	By("unmarshalling the response")
	discoveredConfig := &discovery.ConfigResult{}
	err = json.Unmarshal(sess.Out.Contents(), &discoveredConfig)
	Expect(err).NotTo(HaveOccurred())

	return discoveredConfig
}

func submitSnapshotRequest(n *nwo.Network, channel string, blockNum int, peer *nwo.Peer, expectedMsg string) {
	sess, err := n.PeerAdminSession(peer, commands.SnapshotSubmitRequest{
		ChannelID:   channel,
		BlockNumber: strconv.Itoa(blockNum),
		ClientAuth:  n.ClientAuthRequired,
		PeerAddress: n.PeerAddress(peer, nwo.ListenPort),
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess).To(gbytes.Say(expectedMsg))
}

func verifyNoPendingSnapshotRequest(n *nwo.Network, peer *nwo.Peer, channelID string) {
	cmd := commands.SnapshotListPending{
		ChannelID:   channelID,
		ClientAuth:  n.ClientAuthRequired,
		PeerAddress: n.PeerAddress(peer, nwo.ListenPort),
	}
	checkPending := func() string {
		sess, err := n.PeerAdminSession(peer, cmd)
		Expect(err).NotTo(HaveOccurred())
		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
		return string(sess.Buffer().Contents())
	}
	Eventually(checkPending, n.EventuallyTimeout, 10*time.Second).Should(ContainSubstring("Successfully got pending snapshot requests: []\n"))
}

func joinBySnapshot(n *nwo.Network, orderer *nwo.Orderer, peer *nwo.Peer, channelID string, snapshotDir string) {
	n.JoinChannelBySnapshot(snapshotDir, peer)

	By("calling JoinBySnapshotStatus until joinbysnapshot is completed")
	checkStatus := func() string { return n.JoinBySnapshotStatus(peer) }
	Eventually(checkStatus, n.EventuallyTimeout, 10*time.Second).Should(ContainSubstring("No joinbysnapshot operation is in progress"))

	By("waiting for the peer to have the same ledger height")
	channelHeight := nwo.GetMaxLedgerHeight(n, channelID, n.PeersWithChannel(channelID)...)
	nwo.WaitUntilEqualLedgerHeight(n, channelID, channelHeight, peer)
}
