github.com/hyperledger-labs/bdls@v2.1.1+incompatible/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/hyperledger/fabric/common/util"
    22  	"github.com/hyperledger/fabric/integration/nwo/commands"
    23  	"github.com/hyperledger/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  		Eventually(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  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   218  		Expect(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(`\Qcommitted with status (VALID)\E`))
   250  		}
   251  	}
   252  }
   253  
   254  func CheckCommitReadinessUntilReady(n *Network, channel string, chaincode Chaincode, checkOrgs []*Organization, peers ...*Peer) {
   255  	for _, p := range peers {
   256  		keys := Keys{}
   257  		for _, org := range checkOrgs {
   258  			keys[org.MSPID] = BeTrue()
   259  		}
   260  		Eventually(checkCommitReadiness(n, p, channel, chaincode), n.EventuallyTimeout).Should(MatchKeys(IgnoreExtras, keys))
   261  	}
   262  }
   263  
   264  func CommitChaincode(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peer *Peer, checkPeers ...*Peer) {
   265  	// commit using one peer per org
   266  	commitOrgs := map[string]bool{}
   267  	var peerAddresses []string
   268  	for _, p := range checkPeers {
   269  		if exists := commitOrgs[p.Organization]; !exists {
   270  			peerAddresses = append(peerAddresses, n.PeerAddress(p, ListenPort))
   271  			commitOrgs[p.Organization] = true
   272  		}
   273  	}
   274  
   275  	sess, err := n.PeerAdminSession(peer, commands.ChaincodeCommit{
   276  		ChannelID:           channel,
   277  		Orderer:             n.OrdererAddress(orderer, ListenPort),
   278  		Name:                chaincode.Name,
   279  		Version:             chaincode.Version,
   280  		Sequence:            chaincode.Sequence,
   281  		EndorsementPlugin:   chaincode.EndorsementPlugin,
   282  		ValidationPlugin:    chaincode.ValidationPlugin,
   283  		SignaturePolicy:     chaincode.SignaturePolicy,
   284  		ChannelConfigPolicy: chaincode.ChannelConfigPolicy,
   285  		InitRequired:        chaincode.InitRequired,
   286  		CollectionsConfig:   chaincode.CollectionsConfig,
   287  		PeerAddresses:       peerAddresses,
   288  		ClientAuth:          n.ClientAuthRequired,
   289  	})
   290  	Expect(err).NotTo(HaveOccurred())
   291  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   292  	for i := 0; i < len(peerAddresses); i++ {
   293  		Eventually(sess.Err, n.EventuallyTimeout).Should(gbytes.Say(`\Qcommitted with status (VALID)\E`))
   294  	}
   295  	checkOrgs := []*Organization{}
   296  	for org := range commitOrgs {
   297  		checkOrgs = append(checkOrgs, n.Organization(org))
   298  	}
   299  	EnsureChaincodeCommitted(n, channel, chaincode.Name, chaincode.Version, chaincode.Sequence, checkOrgs, checkPeers...)
   300  }
   301  
   302  // EnsureChaincodeCommitted polls each supplied peer until the chaincode definition
   303  // has been committed to the peer's ledger.
   304  func EnsureChaincodeCommitted(n *Network, channel, name, version, sequence string, checkOrgs []*Organization, peers ...*Peer) {
   305  	for _, p := range peers {
   306  		sequenceInt, err := strconv.ParseInt(sequence, 10, 64)
   307  		Expect(err).NotTo(HaveOccurred())
   308  		approvedKeys := Keys{}
   309  		for _, org := range checkOrgs {
   310  			approvedKeys[org.MSPID] = BeTrue()
   311  		}
   312  		Eventually(listCommitted(n, p, channel, name), n.EventuallyTimeout).Should(
   313  			MatchFields(IgnoreExtras, Fields{
   314  				"Version":   Equal(version),
   315  				"Sequence":  Equal(sequenceInt),
   316  				"Approvals": MatchKeys(IgnoreExtras, approvedKeys),
   317  			}),
   318  		)
   319  	}
   320  }
   321  
   322  func InitChaincode(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
   323  	// init using one peer per org
   324  	initOrgs := map[string]bool{}
   325  	var peerAddresses []string
   326  	for _, p := range peers {
   327  		if exists := initOrgs[p.Organization]; !exists {
   328  			peerAddresses = append(peerAddresses, n.PeerAddress(p, ListenPort))
   329  			initOrgs[p.Organization] = true
   330  		}
   331  	}
   332  
   333  	sess, err := n.PeerUserSession(peers[0], "User1", commands.ChaincodeInvoke{
   334  		ChannelID:     channel,
   335  		Orderer:       n.OrdererAddress(orderer, ListenPort),
   336  		Name:          chaincode.Name,
   337  		Ctor:          chaincode.Ctor,
   338  		PeerAddresses: peerAddresses,
   339  		WaitForEvent:  true,
   340  		IsInit:        true,
   341  		ClientAuth:    n.ClientAuthRequired,
   342  	})
   343  	Expect(err).NotTo(HaveOccurred())
   344  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   345  	for i := 0; i < len(peerAddresses); i++ {
   346  		Eventually(sess.Err, n.EventuallyTimeout).Should(gbytes.Say(`\Qcommitted with status (VALID)\E`))
   347  	}
   348  	Expect(sess.Err).To(gbytes.Say("Chaincode invoke successful. result: status:200"))
   349  }
   350  
   351  func InstantiateChaincodeLegacy(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peer *Peer, checkPeers ...*Peer) {
   352  	sess, err := n.PeerAdminSession(peer, commands.ChaincodeInstantiateLegacy{
   353  		ChannelID:         channel,
   354  		Orderer:           n.OrdererAddress(orderer, ListenPort),
   355  		Name:              chaincode.Name,
   356  		Version:           chaincode.Version,
   357  		Ctor:              chaincode.Ctor,
   358  		Policy:            chaincode.Policy,
   359  		Lang:              chaincode.Lang,
   360  		CollectionsConfig: chaincode.CollectionsConfig,
   361  		ClientAuth:        n.ClientAuthRequired,
   362  	})
   363  	Expect(err).NotTo(HaveOccurred())
   364  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   365  
   366  	EnsureInstantiatedLegacy(n, channel, chaincode.Name, chaincode.Version, checkPeers...)
   367  }
   368  
   369  func EnsureInstantiatedLegacy(n *Network, channel, name, version string, peers ...*Peer) {
   370  	for _, p := range peers {
   371  		Eventually(listInstantiatedLegacy(n, p, channel), n.EventuallyTimeout).Should(
   372  			gbytes.Say(fmt.Sprintf("Name: %s, Version: %s,", name, version)),
   373  		)
   374  	}
   375  }
   376  
   377  func UpgradeChaincodeLegacy(n *Network, channel string, orderer *Orderer, chaincode Chaincode, peers ...*Peer) {
   378  	if len(peers) == 0 {
   379  		peers = n.PeersWithChannel(channel)
   380  	}
   381  	if len(peers) == 0 {
   382  		return
   383  	}
   384  
   385  	// install on all peers
   386  	InstallChaincodeLegacy(n, chaincode, peers...)
   387  
   388  	// upgrade from the first peer
   389  	sess, err := n.PeerAdminSession(peers[0], commands.ChaincodeUpgradeLegacy{
   390  		ChannelID:         channel,
   391  		Orderer:           n.OrdererAddress(orderer, ListenPort),
   392  		Name:              chaincode.Name,
   393  		Version:           chaincode.Version,
   394  		Ctor:              chaincode.Ctor,
   395  		Policy:            chaincode.Policy,
   396  		CollectionsConfig: chaincode.CollectionsConfig,
   397  		ClientAuth:        n.ClientAuthRequired,
   398  	})
   399  	Expect(err).NotTo(HaveOccurred())
   400  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   401  
   402  	EnsureInstantiatedLegacy(n, channel, chaincode.Name, chaincode.Version, peers...)
   403  }
   404  
   405  func EnsureInstalled(n *Network, label, packageID string, peers ...*Peer) {
   406  	for _, p := range peers {
   407  		Eventually(QueryInstalled(n, p), n.EventuallyTimeout).Should(
   408  			ContainElement(MatchFields(IgnoreExtras,
   409  				Fields{
   410  					"Label":     Equal(label),
   411  					"PackageId": Equal(packageID),
   412  				},
   413  			)),
   414  		)
   415  	}
   416  }
   417  
   418  func QueryInstalledReferences(n *Network, channel, label, packageID string, checkPeer *Peer, nameVersions ...[]string) {
   419  	chaincodes := make([]*lifecycle.QueryInstalledChaincodesResult_Chaincode, len(nameVersions))
   420  	for i, nameVersion := range nameVersions {
   421  		chaincodes[i] = &lifecycle.QueryInstalledChaincodesResult_Chaincode{
   422  			Name:    nameVersion[0],
   423  			Version: nameVersion[1],
   424  		}
   425  	}
   426  
   427  	Expect(QueryInstalled(n, checkPeer)()).To(
   428  		ContainElement(MatchFields(IgnoreExtras,
   429  			Fields{
   430  				"Label":     Equal(label),
   431  				"PackageId": Equal(packageID),
   432  				"References": HaveKeyWithValue(channel, PointTo(MatchFields(IgnoreExtras,
   433  					Fields{
   434  						"Chaincodes": ConsistOf(chaincodes),
   435  					},
   436  				))),
   437  			},
   438  		)),
   439  	)
   440  }
   441  
   442  func QueryInstalledNoReferences(n *Network, channel, label, packageID string, checkPeer *Peer) {
   443  }
   444  
   445  type queryInstalledOutput struct {
   446  	InstalledChaincodes []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode `json:"installed_chaincodes"`
   447  }
   448  
   449  func QueryInstalled(n *Network, peer *Peer) func() []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode {
   450  	return func() []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode {
   451  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeQueryInstalled{
   452  			ClientAuth: n.ClientAuthRequired,
   453  		})
   454  		Expect(err).NotTo(HaveOccurred())
   455  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   456  		output := &queryInstalledOutput{}
   457  		err = json.Unmarshal(sess.Out.Contents(), output)
   458  		Expect(err).NotTo(HaveOccurred())
   459  		return output.InstalledChaincodes
   460  	}
   461  }
   462  
   463  type checkCommitReadinessOutput struct {
   464  	Approvals map[string]bool `json:"approvals"`
   465  }
   466  
   467  func checkCommitReadiness(n *Network, peer *Peer, channel string, chaincode Chaincode) func() map[string]bool {
   468  	return func() map[string]bool {
   469  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeCheckCommitReadiness{
   470  			ChannelID:           channel,
   471  			Name:                chaincode.Name,
   472  			Version:             chaincode.Version,
   473  			Sequence:            chaincode.Sequence,
   474  			EndorsementPlugin:   chaincode.EndorsementPlugin,
   475  			ValidationPlugin:    chaincode.ValidationPlugin,
   476  			SignaturePolicy:     chaincode.SignaturePolicy,
   477  			ChannelConfigPolicy: chaincode.ChannelConfigPolicy,
   478  			InitRequired:        chaincode.InitRequired,
   479  			CollectionsConfig:   chaincode.CollectionsConfig,
   480  			ClientAuth:          n.ClientAuthRequired,
   481  		})
   482  		Expect(err).NotTo(HaveOccurred())
   483  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   484  		output := &checkCommitReadinessOutput{}
   485  		err = json.Unmarshal(sess.Out.Contents(), output)
   486  		Expect(err).NotTo(HaveOccurred())
   487  		return output.Approvals
   488  	}
   489  }
   490  
   491  type queryCommittedOutput struct {
   492  	Sequence  int64           `json:"sequence"`
   493  	Version   string          `json:"version"`
   494  	Approvals map[string]bool `json:"approvals"`
   495  }
   496  
   497  // listCommitted returns the result of the queryCommitted command.
   498  // If the command fails for any reason (e.g. namespace not defined
   499  // or a database access issue), it will return an empty output object.
   500  func listCommitted(n *Network, peer *Peer, channel, name string) func() queryCommittedOutput {
   501  	return func() queryCommittedOutput {
   502  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeListCommitted{
   503  			ChannelID:  channel,
   504  			Name:       name,
   505  			ClientAuth: n.ClientAuthRequired,
   506  		})
   507  		Expect(err).NotTo(HaveOccurred())
   508  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   509  		output := &queryCommittedOutput{}
   510  		if sess.ExitCode() == 1 {
   511  			// don't try to unmarshal the output as JSON if the query failed
   512  			return *output
   513  		}
   514  		err = json.Unmarshal(sess.Out.Contents(), output)
   515  		Expect(err).NotTo(HaveOccurred())
   516  		return *output
   517  	}
   518  }
   519  
   520  func listInstantiatedLegacy(n *Network, peer *Peer, channel string) func() *gbytes.Buffer {
   521  	return func() *gbytes.Buffer {
   522  		sess, err := n.PeerAdminSession(peer, commands.ChaincodeListInstantiatedLegacy{
   523  			ChannelID:  channel,
   524  			ClientAuth: n.ClientAuthRequired,
   525  		})
   526  		Expect(err).NotTo(HaveOccurred())
   527  		Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
   528  		return sess.Buffer()
   529  	}
   530  }
   531  
   532  // EnableCapabilities enables a specific capabilities flag for a running network.
   533  // It generates the config update using the first peer, signs the configuration
   534  // with the subsequent peers, and then submits the config update using the
   535  // first peer.
   536  func EnableCapabilities(network *Network, channel, capabilitiesGroup, capabilitiesVersion string, orderer *Orderer, peers ...*Peer) {
   537  	if len(peers) == 0 {
   538  		return
   539  	}
   540  
   541  	config := GetConfig(network, peers[0], orderer, channel)
   542  	updatedConfig := proto.Clone(config).(*common.Config)
   543  
   544  	updatedConfig.ChannelGroup.Groups[capabilitiesGroup].Values["Capabilities"] = &common.ConfigValue{
   545  		ModPolicy: "Admins",
   546  		Value: protoutil.MarshalOrPanic(
   547  			&common.Capabilities{
   548  				Capabilities: map[string]*common.Capability{
   549  					capabilitiesVersion: {},
   550  				},
   551  			},
   552  		),
   553  	}
   554  
   555  	UpdateConfig(network, orderer, channel, config, updatedConfig, false, peers[0], peers...)
   556  }
   557  
   558  // WaitUntilEqualLedgerHeight waits until all specified peers have the
   559  // provided ledger height on a channel
   560  func WaitUntilEqualLedgerHeight(n *Network, channel string, height int, peers ...*Peer) {
   561  	for _, peer := range peers {
   562  		Eventually(func() int {
   563  			return GetLedgerHeight(n, peer, channel)
   564  		}, n.EventuallyTimeout).Should(Equal(height))
   565  	}
   566  }
   567  
   568  // GetLedgerHeight returns the current ledger height for a peer on
   569  // a channel
   570  func GetLedgerHeight(n *Network, peer *Peer, channel string) int {
   571  	sess, err := n.PeerUserSession(peer, "User1", commands.ChannelInfo{
   572  		ChannelID:  channel,
   573  		ClientAuth: n.ClientAuthRequired,
   574  	})
   575  	Expect(err).NotTo(HaveOccurred())
   576  	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit())
   577  
   578  	if sess.ExitCode() == 1 {
   579  		// if org is not yet member of channel, peer will return error
   580  		return -1
   581  	}
   582  
   583  	channelInfoStr := strings.TrimPrefix(string(sess.Buffer().Contents()[:]), "Blockchain info:")
   584  	var channelInfo = common.BlockchainInfo{}
   585  	json.Unmarshal([]byte(channelInfoStr), &channelInfo)
   586  	return int(channelInfo.Height)
   587  }
   588  
   589  // GetMaxLedgerHeight returns the maximum ledger height for the
   590  // peers on a channel
   591  func GetMaxLedgerHeight(n *Network, channel string, peers ...*Peer) int {
   592  	var maxHeight int
   593  	for _, peer := range peers {
   594  		peerHeight := GetLedgerHeight(n, peer, channel)
   595  		if peerHeight > maxHeight {
   596  			maxHeight = peerHeight
   597  		}
   598  	}
   599  	return maxHeight
   600  }