github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+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 type queryInstalledOutput struct { 432 InstalledChaincodes []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode `json:"installed_chaincodes"` 433 } 434 435 func queryInstalled(n *Network, peer *Peer) func() []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode { 436 return func() []lifecycle.QueryInstalledChaincodesResult_InstalledChaincode { 437 sess, err := n.PeerAdminSession(peer, commands.ChaincodeQueryInstalled{ 438 ClientAuth: n.ClientAuthRequired, 439 }) 440 Expect(err).NotTo(HaveOccurred()) 441 Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0)) 442 output := &queryInstalledOutput{} 443 err = json.Unmarshal(sess.Out.Contents(), output) 444 Expect(err).NotTo(HaveOccurred()) 445 return output.InstalledChaincodes 446 } 447 } 448 449 type checkCommitReadinessOutput struct { 450 Approvals map[string]bool `json:"approvals"` 451 } 452 453 func checkCommitReadiness(n *Network, peer *Peer, channel string, chaincode Chaincode) func() map[string]bool { 454 return func() map[string]bool { 455 sess, err := n.PeerAdminSession(peer, commands.ChaincodeCheckCommitReadiness{ 456 ChannelID: channel, 457 Name: chaincode.Name, 458 Version: chaincode.Version, 459 Sequence: chaincode.Sequence, 460 EndorsementPlugin: chaincode.EndorsementPlugin, 461 ValidationPlugin: chaincode.ValidationPlugin, 462 SignaturePolicy: chaincode.SignaturePolicy, 463 ChannelConfigPolicy: chaincode.ChannelConfigPolicy, 464 InitRequired: chaincode.InitRequired, 465 CollectionsConfig: chaincode.CollectionsConfig, 466 ClientAuth: n.ClientAuthRequired, 467 }) 468 Expect(err).NotTo(HaveOccurred()) 469 Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0)) 470 output := &checkCommitReadinessOutput{} 471 err = json.Unmarshal(sess.Out.Contents(), output) 472 Expect(err).NotTo(HaveOccurred()) 473 return output.Approvals 474 } 475 } 476 477 type queryCommittedOutput struct { 478 Sequence int64 `json:"sequence"` 479 Version string `json:"version"` 480 Approvals map[string]bool `json:"approvals"` 481 } 482 483 // listCommitted returns the result of the queryCommitted command. 484 // If the command fails for any reason (e.g. namespace not defined 485 // or a database access issue), it will return an empty output object. 486 func listCommitted(n *Network, peer *Peer, channel, name string) func() queryCommittedOutput { 487 return func() queryCommittedOutput { 488 sess, err := n.PeerAdminSession(peer, commands.ChaincodeListCommitted{ 489 ChannelID: channel, 490 Name: name, 491 ClientAuth: n.ClientAuthRequired, 492 }) 493 Expect(err).NotTo(HaveOccurred()) 494 Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit()) 495 output := &queryCommittedOutput{} 496 if sess.ExitCode() == 1 { 497 // don't try to unmarshal the output as JSON if the query failed 498 return *output 499 } 500 err = json.Unmarshal(sess.Out.Contents(), output) 501 Expect(err).NotTo(HaveOccurred()) 502 return *output 503 } 504 } 505 506 func listInstantiatedLegacy(n *Network, peer *Peer, channel string) func() *gbytes.Buffer { 507 return func() *gbytes.Buffer { 508 sess, err := n.PeerAdminSession(peer, commands.ChaincodeListInstantiatedLegacy{ 509 ChannelID: channel, 510 ClientAuth: n.ClientAuthRequired, 511 }) 512 Expect(err).NotTo(HaveOccurred()) 513 Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0)) 514 return sess.Buffer() 515 } 516 } 517 518 // EnableCapabilities enables a specific capabilities flag for a running network. 519 // It generates the config update using the first peer, signs the configuration 520 // with the subsequent peers, and then submits the config update using the 521 // first peer. 522 func EnableCapabilities(network *Network, channel, capabilitiesGroup, capabilitiesVersion string, orderer *Orderer, peers ...*Peer) { 523 if len(peers) == 0 { 524 return 525 } 526 527 config := GetConfig(network, peers[0], orderer, channel) 528 updatedConfig := proto.Clone(config).(*common.Config) 529 530 updatedConfig.ChannelGroup.Groups[capabilitiesGroup].Values["Capabilities"] = &common.ConfigValue{ 531 ModPolicy: "Admins", 532 Value: protoutil.MarshalOrPanic( 533 &common.Capabilities{ 534 Capabilities: map[string]*common.Capability{ 535 capabilitiesVersion: {}, 536 }, 537 }, 538 ), 539 } 540 541 UpdateConfig(network, orderer, channel, config, updatedConfig, false, peers[0], peers...) 542 } 543 544 // WaitUntilEqualLedgerHeight waits until all specified peers have the 545 // provided ledger height on a channel 546 func WaitUntilEqualLedgerHeight(n *Network, channel string, height int, peers ...*Peer) { 547 for _, peer := range peers { 548 Eventually(func() int { 549 return GetLedgerHeight(n, peer, channel) 550 }, n.EventuallyTimeout).Should(Equal(height)) 551 } 552 } 553 554 // GetLedgerHeight returns the current ledger height for a peer on 555 // a channel 556 func GetLedgerHeight(n *Network, peer *Peer, channel string) int { 557 sess, err := n.PeerUserSession(peer, "User1", commands.ChannelInfo{ 558 ChannelID: channel, 559 ClientAuth: n.ClientAuthRequired, 560 }) 561 Expect(err).NotTo(HaveOccurred()) 562 Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit()) 563 564 if sess.ExitCode() == 1 { 565 // if org is not yet member of channel, peer will return error 566 return -1 567 } 568 569 channelInfoStr := strings.TrimPrefix(string(sess.Buffer().Contents()[:]), "Blockchain info:") 570 var channelInfo = common.BlockchainInfo{} 571 json.Unmarshal([]byte(channelInfoStr), &channelInfo) 572 return int(channelInfo.Height) 573 } 574 575 // GetMaxLedgerHeight returns the maximum ledger height for the 576 // peers on a channel 577 func GetMaxLedgerHeight(n *Network, channel string, peers ...*Peer) int { 578 var maxHeight int 579 for _, peer := range peers { 580 peerHeight := GetLedgerHeight(n, peer, channel) 581 if peerHeight > maxHeight { 582 maxHeight = peerHeight 583 } 584 } 585 return maxHeight 586 }