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 }