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