github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/integration/nwo/deploy.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package nwo
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/golang/protobuf/proto"
    19  	"github.com/hyperledger/fabric-protos-go/common"
    20  	"github.com/hyperledger/fabric-protos-go/peer/lifecycle"
    21  	"github.com/osdi23p228/fabric/common/util"
    22  	"github.com/osdi23p228/fabric/integration/nwo/commands"
    23  	"github.com/osdi23p228/fabric/protoutil"
    24  	"github.com/onsi/ginkgo"
    25  	. "github.com/onsi/gomega"
    26  	"github.com/onsi/gomega/gbytes"
    27  	"github.com/onsi/gomega/gexec"
    28  	. "github.com/onsi/gomega/gstruct"
    29  )
    30  
    31  type Chaincode struct {
    32  	Name                string
    33  	Version             string
    34  	Path                string
    35  	Ctor                string
    36  	Policy              string // only used for legacy lifecycle. For new lifecycle use SignaturePolicy
    37  	Lang                string
    38  	CollectionsConfig   string // optional
    39  	PackageFile         string
    40  	PackageID           string            // if unspecified, chaincode won't be executable. Can use SetPackageIDFromPackageFile() to set.
    41  	CodeFiles           map[string]string // map from paths on the filesystem to code.tar.gz paths
    42  	Sequence            string
    43  	EndorsementPlugin   string
    44  	ValidationPlugin    string
    45  	InitRequired        bool
    46  	Label               string
    47  	SignaturePolicy     string
    48  	ChannelConfigPolicy string
    49  }
    50  
    51  func (c *Chaincode) SetPackageIDFromPackageFile() {
    52  	fileBytes, err := ioutil.ReadFile(c.PackageFile)
    53  	Expect(err).NotTo(HaveOccurred())
    54  	hashStr := fmt.Sprintf("%x", util.ComputeSHA256(fileBytes))
    55  	c.PackageID = c.Label + ":" + hashStr
    56  }
    57  
    58  // DeployChaincode is a helper that will install chaincode to all peers that
    59  // are connected to the specified channel, approve the chaincode on one of the
    60  // peers of each organization in the network, commit the chaincode definition
    61  // on the channel using one of the peers, and wait for the chaincode commit to
    62  // complete on all of the peers. It uses the _lifecycle implementation.
    63  // NOTE V2_0 capabilities must be enabled for this functionality to work.
    64  func DeployChaincode(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
    65  	if len(peers) == 0 {
    66  		peers = n.PeersWithChannel(channel)
    67  	}
    68  	if len(peers) == 0 {
    69  		return
    70  	}
    71  
    72  	PackageAndInstallChaincode(n, chaincode, peers...)
    73  
    74  	// approve for each org
    75  	ApproveChaincodeForMyOrg(n, channel, orderer, chaincode, peers...)
    76  
    77  	// commit definition
    78  	CheckCommitReadinessUntilReady(n, channel, chaincode, n.PeerOrgs(), peers...)
    79  	CommitChaincode(n, channel, orderer, chaincode, peers[0], peers...)
    80  
    81  	// init the chaincode, if required
    82  	if chaincode.InitRequired {
    83  		InitChaincode(n, channel, orderer, chaincode, peers...)
    84  	}
    85  }
    86  
    87  // DeployChaincodeLegacy is a helper that will install chaincode to all peers
    88  // that are connected to the specified channel, instantiate the chaincode on
    89  // one of the peers, and wait for the instantiation to complete on all of the
    90  // peers. It uses the legacy lifecycle (lscc) implementation.
    91  //
    92  // NOTE: This helper should not be used to deploy the same chaincode on
    93  // multiple channels as the install will fail on subsequent calls. Instead,
    94  // simply use InstantiateChaincode().
    95  func DeployChaincodeLegacy(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
    96  	if len(peers) == 0 {
    97  		peers = n.PeersWithChannel(channel)
    98  	}
    99  	if len(peers) == 0 {
   100  		return
   101  	}
   102  
   103  	// create temp file for chaincode package if not provided
   104  	if chaincode.PackageFile == "" {
   105  		tempFile, err := ioutil.TempFile("", "chaincode-package")
   106  		Expect(err).NotTo(HaveOccurred())
   107  		tempFile.Close()
   108  		defer os.Remove(tempFile.Name())
   109  		chaincode.PackageFile = tempFile.Name()
   110  	}
   111  
   112  	// only create chaincode package if it doesn't already exist
   113  	if fi, err := os.Stat(chaincode.PackageFile); os.IsNotExist(err) || fi.Size() == 0 {
   114  		PackageChaincodeLegacy(n, chaincode, peers[0])
   115  	}
   116  
   117  	// install on all peers
   118  	InstallChaincodeLegacy(n, chaincode, peers...)
   119  
   120  	// instantiate on the first peer
   121  	InstantiateChaincodeLegacy(n, channel, orderer, chaincode, peers[0], peers...)
   122  }
   123  
   124  func PackageAndInstallChaincode(n *Network, chaincode Chaincode, peers ...*Peer) {
   125  	// create temp file for chaincode package if not provided
   126  	if chaincode.PackageFile == "" {
   127  		tempFile, err := ioutil.TempFile("", "chaincode-package")
   128  		Expect(err).NotTo(HaveOccurred())
   129  		tempFile.Close()
   130  		defer os.Remove(tempFile.Name())
   131  		chaincode.PackageFile = tempFile.Name()
   132  	}
   133  
   134  	// only create chaincode package if it doesn't already exist
   135  	if _, err := os.Stat(chaincode.PackageFile); os.IsNotExist(err) {
   136  		switch chaincode.Lang {
   137  		case "binary":
   138  			PackageChaincodeBinary(chaincode)
   139  		default:
   140  			PackageChaincode(n, chaincode, peers[0])
   141  		}
   142  	}
   143  
   144  	// install on all peers
   145  	InstallChaincode(n, chaincode, peers...)
   146  }
   147  
   148  func PackageChaincode(n *Network, chaincode Chaincode, peer *Peer) {
   149  	sess, err := n.PeerAdminSession(peer, commands.ChaincodePackage{
   150  		Path:       chaincode.Path,
   151  		Lang:       chaincode.Lang,
   152  		Label:      chaincode.Label,
   153  		OutputFile: chaincode.PackageFile,
   154  		ClientAuth: n.ClientAuthRequired,
   155  	})
   156  	Expect(err).NotTo(HaveOccurred())
   157  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   158  }
   159  
   160  func PackageChaincodeLegacy(n *Network, chaincode Chaincode, peer *Peer) {
   161  	sess, err := n.PeerAdminSession(peer, commands.ChaincodePackageLegacy{
   162  		Name:       chaincode.Name,
   163  		Version:    chaincode.Version,
   164  		Path:       chaincode.Path,
   165  		Lang:       chaincode.Lang,
   166  		OutputFile: chaincode.PackageFile,
   167  		ClientAuth: n.ClientAuthRequired,
   168  	})
   169  	Expect(err).NotTo(HaveOccurred())
   170  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   171  }
   172  
   173  func InstallChaincode(n *Network, chaincode Chaincode, peers ...*Peer) {
   174  	// Ensure 'jq' exists in path, because we need it to build chaincode
   175  	if _, err := exec.LookPath("jq"); err != nil {
   176  		ginkgo.Fail("'jq' is needed to build chaincode but it wasn't found in the PATH")
   177  	}
   178  
   179  	if chaincode.PackageID == "" {
   180  		chaincode.SetPackageIDFromPackageFile()
   181  	}
   182  
   183  	for _, p := range peers {
   184  		sess, err := n.PeerAdminSession(p, commands.ChaincodeInstall{
   185  			PackageFile: chaincode.PackageFile,
   186  			ClientAuth:  n.ClientAuthRequired,
   187  		})
   188  		ExpectWithOffset(1, err).NotTo(HaveOccurred())
   189  		EventuallyWithOffset(1, sess, n.EventuallyTimeout).Should(gexec.Exit())
   190  
   191  		EnsureInstalled(n, chaincode.Label, chaincode.PackageID, p)
   192  	}
   193  }
   194  
   195  func InstallChaincodeLegacy(n *Network, chaincode Chaincode, peers ...*Peer) {
   196  	// Ensure 'jq' exists in path, because we need it to build chaincode
   197  	if _, err := exec.LookPath("jq"); err != nil {
   198  		ginkgo.Fail("'jq' is needed to build chaincode but it wasn't found in the PATH")
   199  	}
   200  
   201  	for _, p := range peers {
   202  		sess, err := n.PeerAdminSession(p, commands.ChaincodeInstallLegacy{
   203  			Name:        chaincode.Name,
   204  			Version:     chaincode.Version,
   205  			Path:        chaincode.Path,
   206  			Lang:        chaincode.Lang,
   207  			PackageFile: chaincode.PackageFile,
   208  			ClientAuth:  n.ClientAuthRequired,
   209  		})
   210  		Expect(err).NotTo(HaveOccurred())
   211  		EventuallyWithOffset(1, sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   212  
   213  		sess, err = n.PeerAdminSession(p, commands.ChaincodeListInstalledLegacy{
   214  			ClientAuth: n.ClientAuthRequired,
   215  		})
   216  		Expect(err).NotTo(HaveOccurred())
   217  		EventuallyWithOffset(1, sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   218  		ExpectWithOffset(1, sess).To(gbytes.Say(fmt.Sprintf("Name: %s, Version: %s,", chaincode.Name, chaincode.Version)))
   219  	}
   220  }
   221  
   222  func ApproveChaincodeForMyOrg(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
   223  	if chaincode.PackageID == "" {
   224  		chaincode.SetPackageIDFromPackageFile()
   225  	}
   226  
   227  	// used to ensure we only approve once per org
   228  	approvedOrgs := map[string]bool{}
   229  	for _, p := range peers {
   230  		if _, ok := approvedOrgs[p.Organization]; !ok {
   231  			sess, err := n.PeerAdminSession(p, commands.ChaincodeApproveForMyOrg{
   232  				ChannelID:           channel,
   233  				Orderer:             n.OrdererAddress(orderer, ListenPort),
   234  				Name:                chaincode.Name,
   235  				Version:             chaincode.Version,
   236  				PackageID:           chaincode.PackageID,
   237  				Sequence:            chaincode.Sequence,
   238  				EndorsementPlugin:   chaincode.EndorsementPlugin,
   239  				ValidationPlugin:    chaincode.ValidationPlugin,
   240  				SignaturePolicy:     chaincode.SignaturePolicy,
   241  				ChannelConfigPolicy: chaincode.ChannelConfigPolicy,
   242  				InitRequired:        chaincode.InitRequired,
   243  				CollectionsConfig:   chaincode.CollectionsConfig,
   244  				ClientAuth:          n.ClientAuthRequired,
   245  			})
   246  			Expect(err).NotTo(HaveOccurred())
   247  			Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   248  			approvedOrgs[p.Organization] = true
   249  			Eventually(sess.Err, n.EventuallyTimeout).Should(gbytes.Say(fmt.Sprintf(`\Qcommitted with status (VALID) at %s\E`, n.PeerAddress(p, ListenPort))))
   250  		}
   251  	}
   252  }
   253  
   254  func EnsureChaincodeApproved(n *Network, peer *Peer, channel, name, sequence string) {
   255  	sequenceInt, err := strconv.ParseInt(sequence, 10, 64)
   256  	Expect(err).NotTo(HaveOccurred())
   257  	Eventually(queryApproved(n, peer, channel, name, sequence), n.EventuallyTimeout).Should(
   258  		MatchFields(IgnoreExtras, Fields{
   259  			"Sequence": Equal(sequenceInt),
   260  		}),
   261  	)
   262  }
   263  
   264  func CheckCommitReadinessUntilReady(n *Network, channel string, chaincode Chaincode, checkOrgs []*Organization, peers ...*Peer) {
   265  	for _, p := range peers {
   266  		keys := Keys{}
   267  		for _, org := range checkOrgs {
   268  			keys[org.MSPID] = BeTrue()
   269  		}
   270  		Eventually(checkCommitReadiness(n, p, channel, chaincode), n.EventuallyTimeout).Should(MatchKeys(IgnoreExtras, keys))
   271  	}
   272  }
   273  
   274  func CommitChaincode(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peer *Peer, checkPeers ...*Peer) {
   275  	// commit using one peer per org
   276  	commitOrgs := map[string]bool{}
   277  	var peerAddresses []string
   278  	for _, p := range checkPeers {
   279  		if exists := commitOrgs[p.Organization]; !exists {
   280  			peerAddresses = append(peerAddresses, n.PeerAddress(p, ListenPort))
   281  			commitOrgs[p.Organization] = true
   282  		}
   283  	}
   284  
   285  	sess, err := n.PeerAdminSession(peer, commands.ChaincodeCommit{
   286  		ChannelID:           channel,
   287  		Orderer:             n.OrdererAddress(orderer, ListenPort),
   288  		Name:                chaincode.Name,
   289  		Version:             chaincode.Version,
   290  		Sequence:            chaincode.Sequence,
   291  		EndorsementPlugin:   chaincode.EndorsementPlugin,
   292  		ValidationPlugin:    chaincode.ValidationPlugin,
   293  		SignaturePolicy:     chaincode.SignaturePolicy,
   294  		ChannelConfigPolicy: chaincode.ChannelConfigPolicy,
   295  		InitRequired:        chaincode.InitRequired,
   296  		CollectionsConfig:   chaincode.CollectionsConfig,
   297  		PeerAddresses:       peerAddresses,
   298  		ClientAuth:          n.ClientAuthRequired,
   299  	})
   300  	Expect(err).NotTo(HaveOccurred())
   301  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   302  	for i := 0; i < len(peerAddresses); i++ {
   303  		Eventually(sess.Err, n.EventuallyTimeout).Should(gbytes.Say(`\Qcommitted with status (VALID)\E`))
   304  	}
   305  	checkOrgs := []*Organization{}
   306  	for org := range commitOrgs {
   307  		checkOrgs = append(checkOrgs, n.Organization(org))
   308  	}
   309  	EnsureChaincodeCommitted(n, channel, chaincode.Name, chaincode.Version, chaincode.Sequence, checkOrgs, checkPeers...)
   310  }
   311  
   312  // EnsureChaincodeCommitted polls each supplied peer until the chaincode definition
   313  // has been committed to the peer's ledger.
   314  func EnsureChaincodeCommitted(n *Network, channel, name, version, sequence string, checkOrgs []*Organization, peers ...*Peer) {
   315  	for _, p := range peers {
   316  		sequenceInt, err := strconv.ParseInt(sequence, 10, 64)
   317  		Expect(err).NotTo(HaveOccurred())
   318  		approvedKeys := Keys{}
   319  		for _, org := range checkOrgs {
   320  			approvedKeys[org.MSPID] = BeTrue()
   321  		}
   322  		Eventually(listCommitted(n, p, channel, name), n.EventuallyTimeout).Should(
   323  			MatchFields(IgnoreExtras, Fields{
   324  				"Version":   Equal(version),
   325  				"Sequence":  Equal(sequenceInt),
   326  				"Approvals": MatchKeys(IgnoreExtras, approvedKeys),
   327  			}),
   328  		)
   329  	}
   330  }
   331  
   332  func InitChaincode(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
   333  	// init using one peer per org
   334  	initOrgs := map[string]bool{}
   335  	var peerAddresses []string
   336  	for _, p := range peers {
   337  		if exists := initOrgs[p.Organization]; !exists {
   338  			peerAddresses = append(peerAddresses, n.PeerAddress(p, ListenPort))
   339  			initOrgs[p.Organization] = true
   340  		}
   341  	}
   342  
   343  	sess, err := n.PeerUserSession(peers[0], "User1", commands.ChaincodeInvoke{
   344  		ChannelID:     channel,
   345  		Orderer:       n.OrdererAddress(orderer, ListenPort),
   346  		Name:          chaincode.Name,
   347  		Ctor:          chaincode.Ctor,
   348  		PeerAddresses: peerAddresses,
   349  		WaitForEvent:  true,
   350  		IsInit:        true,
   351  		ClientAuth:    n.ClientAuthRequired,
   352  	})
   353  	Expect(err).NotTo(HaveOccurred())
   354  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   355  	for i := 0; i < len(peerAddresses); i++ {
   356  		Eventually(sess.Err, n.EventuallyTimeout).Should(gbytes.Say(`\Qcommitted with status (VALID)\E`))
   357  	}
   358  	Expect(sess.Err).To(gbytes.Say("Chaincode invoke successful. result: status:200"))
   359  }
   360  
   361  func InstantiateChaincodeLegacy(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peer *Peer, checkPeers ...*Peer) {
   362  	sess, err := n.PeerAdminSession(peer, commands.ChaincodeInstantiateLegacy{
   363  		ChannelID:         channel,
   364  		Orderer:           n.OrdererAddress(orderer, ListenPort),
   365  		Name:              chaincode.Name,
   366  		Version:           chaincode.Version,
   367  		Ctor:              chaincode.Ctor,
   368  		Policy:            chaincode.Policy,
   369  		Lang:              chaincode.Lang,
   370  		CollectionsConfig: chaincode.CollectionsConfig,
   371  		ClientAuth:        n.ClientAuthRequired,
   372  	})
   373  	Expect(err).NotTo(HaveOccurred())
   374  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   375  
   376  	EnsureInstantiatedLegacy(n, channel, chaincode.Name, chaincode.Version, checkPeers...)
   377  }
   378  
   379  func EnsureInstantiatedLegacy(n *Network, channel, name, version string, peers ...*Peer) {
   380  	for _, p := range peers {
   381  		Eventually(listInstantiatedLegacy(n, p, channel), n.EventuallyTimeout).Should(
   382  			gbytes.Say(fmt.Sprintf("Name: %s, Version: %s,", name, version)),
   383  		)
   384  	}
   385  }
   386  
   387  func UpgradeChaincodeLegacy(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
   388  	if len(peers) == 0 {
   389  		peers = n.PeersWithChannel(channel)
   390  	}
   391  	if len(peers) == 0 {
   392  		return
   393  	}
   394  
   395  	// install on all peers
   396  	InstallChaincodeLegacy(n, chaincode, peers...)
   397  
   398  	// upgrade from the first peer
   399  	sess, err := n.PeerAdminSession(peers[0], commands.ChaincodeUpgradeLegacy{
   400  		ChannelID:         channel,
   401  		Orderer:           n.OrdererAddress(orderer, ListenPort),
   402  		Name:              chaincode.Name,
   403  		Version:           chaincode.Version,
   404  		Ctor:              chaincode.Ctor,
   405  		Policy:            chaincode.Policy,
   406  		CollectionsConfig: chaincode.CollectionsConfig,
   407  		ClientAuth:        n.ClientAuthRequired,
   408  	})
   409  	Expect(err).NotTo(HaveOccurred())
   410  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   411  
   412  	EnsureInstantiatedLegacy(n, channel, chaincode.Name, chaincode.Version, peers...)
   413  }
   414  
   415  func EnsureInstalled(n *Network, label, packageID string, peers ...*Peer) {
   416  	for _, p := range peers {
   417  		Eventually(QueryInstalled(n, p), n.EventuallyTimeout).Should(
   418  			ContainElement(MatchFields(IgnoreExtras,
   419  				Fields{
   420  					"Label":     Equal(label),
   421  					"PackageId": Equal(packageID),
   422  				},
   423  			)),
   424  		)
   425  	}
   426  }
   427  
   428  func QueryInstalledReferences(n *Network, channel, label, packageID string, checkPeer *Peer, nameVersions ...[]string) {
   429  	chaincodes := make([]*lifecycle.QueryInstalledChaincodesResult_Chaincode, len(nameVersions))
   430  	for i, nameVersion := range nameVersions {
   431  		chaincodes[i] = &lifecycle.QueryInstalledChaincodesResult_Chaincode{
   432  			Name:    nameVersion[0],
   433  			Version: nameVersion[1],
   434  		}
   435  	}
   436  
   437  	Expect(QueryInstalled(n, checkPeer)()).To(
   438  		ContainElement(MatchFields(IgnoreExtras,
   439  			Fields{
   440  				"Label":     Equal(label),
   441  				"PackageId": Equal(packageID),
   442  				"References": HaveKeyWithValue(channel, PointTo(MatchFields(IgnoreExtras,
   443  					Fields{
   444  						"Chaincodes": ConsistOf(chaincodes),
   445  					},
   446  				))),
   447  			},
   448  		)),
   449  	)
   450  }
   451  
   452  type queryInstalledOutput struct {
   453  	InstalledChaincodes []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode `json:"installed_chaincodes"`
   454  }
   455  
   456  func QueryInstalled(n *Network, peer *Peer) func() []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode {
   457  	return func() []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode {
   458  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeQueryInstalled{
   459  			ClientAuth: n.ClientAuthRequired,
   460  		})
   461  		Expect(err).NotTo(HaveOccurred())
   462  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   463  		output := &queryInstalledOutput{}
   464  		err = json.Unmarshal(sess.Out.Contents(), output)
   465  		Expect(err).NotTo(HaveOccurred())
   466  		return output.InstalledChaincodes
   467  	}
   468  }
   469  
   470  type checkCommitReadinessOutput struct {
   471  	Approvals map[string]bool `json:"approvals"`
   472  }
   473  
   474  func checkCommitReadiness(n *Network, peer *Peer, channel string, chaincode Chaincode) func() map[string]bool {
   475  	return func() map[string]bool {
   476  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeCheckCommitReadiness{
   477  			ChannelID:           channel,
   478  			Name:                chaincode.Name,
   479  			Version:             chaincode.Version,
   480  			Sequence:            chaincode.Sequence,
   481  			EndorsementPlugin:   chaincode.EndorsementPlugin,
   482  			ValidationPlugin:    chaincode.ValidationPlugin,
   483  			SignaturePolicy:     chaincode.SignaturePolicy,
   484  			ChannelConfigPolicy: chaincode.ChannelConfigPolicy,
   485  			InitRequired:        chaincode.InitRequired,
   486  			CollectionsConfig:   chaincode.CollectionsConfig,
   487  			ClientAuth:          n.ClientAuthRequired,
   488  		})
   489  		Expect(err).NotTo(HaveOccurred())
   490  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   491  		output := &checkCommitReadinessOutput{}
   492  		err = json.Unmarshal(sess.Out.Contents(), output)
   493  		Expect(err).NotTo(HaveOccurred())
   494  		return output.Approvals
   495  	}
   496  }
   497  
   498  type queryApprovedOutput struct {
   499  	Sequence int64 `json:"sequence"`
   500  }
   501  
   502  // queryApproved returns the result of the queryApproved command.
   503  // If the command fails for any reason, it will return an empty output object.
   504  func queryApproved(n *Network, peer *Peer, channel, name, sequence string) func() queryApprovedOutput {
   505  	return func() queryApprovedOutput {
   506  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeQueryApproved{
   507  			ChannelID:     channel,
   508  			Name:          name,
   509  			Sequence:      sequence,
   510  			PeerAddresses: []string{n.PeerAddress(peer, ListenPort)},
   511  			ClientAuth:    n.ClientAuthRequired,
   512  		})
   513  		Expect(err).NotTo(HaveOccurred())
   514  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   515  		output := &queryApprovedOutput{}
   516  		if sess.ExitCode() == 1 {
   517  			// don't try to unmarshal the output as JSON if the query failed
   518  			return *output
   519  		}
   520  		err = json.Unmarshal(sess.Out.Contents(), output)
   521  		Expect(err).NotTo(HaveOccurred())
   522  		return *output
   523  	}
   524  }
   525  
   526  type queryCommittedOutput struct {
   527  	Sequence  int64           `json:"sequence"`
   528  	Version   string          `json:"version"`
   529  	Approvals map[string]bool `json:"approvals"`
   530  }
   531  
   532  // listCommitted returns the result of the queryCommitted command.
   533  // If the command fails for any reason (e.g. namespace not defined
   534  // or a database access issue), it will return an empty output object.
   535  func listCommitted(n *Network, peer *Peer, channel, name string) func() queryCommittedOutput {
   536  	return func() queryCommittedOutput {
   537  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeListCommitted{
   538  			ChannelID:  channel,
   539  			Name:       name,
   540  			ClientAuth: n.ClientAuthRequired,
   541  		})
   542  		Expect(err).NotTo(HaveOccurred())
   543  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   544  		output := &queryCommittedOutput{}
   545  		if sess.ExitCode() == 1 {
   546  			// don't try to unmarshal the output as JSON if the query failed
   547  			return *output
   548  		}
   549  		err = json.Unmarshal(sess.Out.Contents(), output)
   550  		Expect(err).NotTo(HaveOccurred())
   551  		return *output
   552  	}
   553  }
   554  
   555  func listInstantiatedLegacy(n *Network, peer *Peer, channel string) func() *gbytes.Buffer {
   556  	return func() *gbytes.Buffer {
   557  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeListInstantiatedLegacy{
   558  			ChannelID:  channel,
   559  			ClientAuth: n.ClientAuthRequired,
   560  		})
   561  		Expect(err).NotTo(HaveOccurred())
   562  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   563  		return sess.Buffer()
   564  	}
   565  }
   566  
   567  // EnableCapabilities enables a specific capabilities flag for a running network.
   568  // It generates the config update using the first peer, signs the configuration
   569  // with the subsequent peers, and then submits the config update using the
   570  // first peer.
   571  func EnableCapabilities(network *Network, channel, capabilitiesGroup, capabilitiesVersion string, orderer *Orderer, peers ...*Peer) {
   572  	if len(peers) == 0 {
   573  		return
   574  	}
   575  
   576  	config := GetConfig(network, peers[0], orderer, channel)
   577  	updatedConfig := proto.Clone(config).(*common.Config)
   578  
   579  	updatedConfig.ChannelGroup.Groups[capabilitiesGroup].Values["Capabilities"] = &common.ConfigValue{
   580  		ModPolicy: "Admins",
   581  		Value: protoutil.MarshalOrPanic(
   582  			&common.Capabilities{
   583  				Capabilities: map[string]*common.Capability{
   584  					capabilitiesVersion: {},
   585  				},
   586  			},
   587  		),
   588  	}
   589  
   590  	UpdateConfig(network, orderer, channel, config, updatedConfig, false, peers[0], peers...)
   591  }
   592  
   593  // WaitUntilEqualLedgerHeight waits until all specified peers have the
   594  // provided ledger height on a channel
   595  func WaitUntilEqualLedgerHeight(n *Network, channel string, height int, peers ...*Peer) {
   596  	for _, peer := range peers {
   597  		Eventually(func() int {
   598  			return GetLedgerHeight(n, peer, channel)
   599  		}, n.EventuallyTimeout).Should(Equal(height))
   600  	}
   601  }
   602  
   603  // GetLedgerHeight returns the current ledger height for a peer on
   604  // a channel
   605  func GetLedgerHeight(n *Network, peer *Peer, channel string) int {
   606  	sess, err := n.PeerUserSession(peer, "User1", commands.ChannelInfo{
   607  		ChannelID:  channel,
   608  		ClientAuth: n.ClientAuthRequired,
   609  	})
   610  	Expect(err).NotTo(HaveOccurred())
   611  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   612  
   613  	if sess.ExitCode() == 1 {
   614  		// if org is not yet member of channel, peer will return error
   615  		return -1
   616  	}
   617  
   618  	channelInfoStr := strings.TrimPrefix(string(sess.Buffer().Contents()[:]), "Blockchain info:")
   619  	var channelInfo = common.BlockchainInfo{}
   620  	json.Unmarshal([]byte(channelInfoStr), &channelInfo)
   621  	return int(channelInfo.Height)
   622  }
   623  
   624  // GetMaxLedgerHeight returns the maximum ledger height for the
   625  // peers on a channel
   626  func GetMaxLedgerHeight(n *Network, channel string, peers ...*Peer) int {
   627  	var maxHeight int
   628  	for _, peer := range peers {
   629  		peerHeight := GetLedgerHeight(n, peer, channel)
   630  		if peerHeight > maxHeight {
   631  			maxHeight = peerHeight
   632  		}
   633  	}
   634  	return maxHeight
   635  }