github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/peer/chaincode/common.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package chaincode 8 9 import ( 10 "context" 11 "crypto/tls" 12 "encoding/json" 13 "fmt" 14 "io/ioutil" 15 "math" 16 "strings" 17 "sync" 18 19 "github.com/golang/protobuf/proto" 20 "github.com/hechain20/hechain/bccsp" 21 "github.com/hechain20/hechain/common/policydsl" 22 "github.com/hechain20/hechain/common/util" 23 "github.com/hechain20/hechain/internal/peer/common" 24 "github.com/hechain20/hechain/internal/pkg/identity" 25 "github.com/hechain20/hechain/protoutil" 26 "github.com/hyperledger/fabric-chaincode-go/shim" 27 pcommon "github.com/hyperledger/fabric-protos-go/common" 28 ab "github.com/hyperledger/fabric-protos-go/orderer" 29 pb "github.com/hyperledger/fabric-protos-go/peer" 30 "github.com/pkg/errors" 31 "github.com/spf13/cobra" 32 "github.com/spf13/viper" 33 ) 34 35 // checkSpec to see if chaincode resides within current package capture for language. 36 func checkSpec(spec *pb.ChaincodeSpec) error { 37 // Don't allow nil value 38 if spec == nil { 39 return errors.New("expected chaincode specification, nil received") 40 } 41 if spec.ChaincodeId == nil { 42 return errors.New("expected chaincode ID, nil received") 43 } 44 45 return platformRegistry.ValidateSpec(spec.Type.String(), spec.ChaincodeId.Path) 46 } 47 48 // getChaincodeDeploymentSpec get chaincode deployment spec given the chaincode spec 49 func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) { 50 var codePackageBytes []byte 51 if crtPkg { 52 var err error 53 if err = checkSpec(spec); err != nil { 54 return nil, err 55 } 56 57 codePackageBytes, err = platformRegistry.GetDeploymentPayload(spec.Type.String(), spec.ChaincodeId.Path) 58 if err != nil { 59 return nil, errors.WithMessage(err, "error getting chaincode package bytes") 60 } 61 chaincodePath, err := platformRegistry.NormalizePath(spec.Type.String(), spec.ChaincodeId.Path) 62 if err != nil { 63 return nil, errors.WithMessage(err, "failed to normalize chaincode path") 64 } 65 spec.ChaincodeId.Path = chaincodePath 66 } 67 68 return &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}, nil 69 } 70 71 // getChaincodeSpec get chaincode spec from the cli cmd parameters 72 func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) { 73 spec := &pb.ChaincodeSpec{} 74 if err := checkChaincodeCmdParams(cmd); err != nil { 75 // unset usage silence because it's a command line usage error 76 cmd.SilenceUsage = false 77 return spec, err 78 } 79 80 // Build the spec 81 input := chaincodeInput{} 82 if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil { 83 return spec, errors.Wrap(err, "chaincode argument error") 84 } 85 input.IsInit = isInit 86 87 chaincodeLang = strings.ToUpper(chaincodeLang) 88 spec = &pb.ChaincodeSpec{ 89 Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]), 90 ChaincodeId: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName, Version: chaincodeVersion}, 91 Input: &input.ChaincodeInput, 92 } 93 return spec, nil 94 } 95 96 // chaincodeInput is wrapper around the proto defined ChaincodeInput message that 97 // is decorated with a custom JSON unmarshaller. 98 type chaincodeInput struct { 99 pb.ChaincodeInput 100 } 101 102 // UnmarshalJSON converts the string-based REST/JSON input to 103 // the []byte-based current ChaincodeInput structure. 104 func (c *chaincodeInput) UnmarshalJSON(b []byte) error { 105 sa := struct { 106 Function string 107 Args []string 108 }{} 109 err := json.Unmarshal(b, &sa) 110 if err != nil { 111 return err 112 } 113 allArgs := sa.Args 114 if sa.Function != "" { 115 allArgs = append([]string{sa.Function}, sa.Args...) 116 } 117 c.Args = util.ToChaincodeArgs(allArgs...) 118 return nil 119 } 120 121 func chaincodeInvokeOrQuery(cmd *cobra.Command, invoke bool, cf *ChaincodeCmdFactory) (err error) { 122 spec, err := getChaincodeSpec(cmd) 123 if err != nil { 124 return err 125 } 126 127 // call with empty txid to ensure production code generates a txid. 128 // otherwise, tests can explicitly set their own txid 129 txID := "" 130 131 proposalResp, err := ChaincodeInvokeOrQuery( 132 spec, 133 channelID, 134 txID, 135 invoke, 136 cf.Signer, 137 cf.Certificate, 138 cf.EndorserClients, 139 cf.DeliverClients, 140 cf.BroadcastClient, 141 ) 142 if err != nil { 143 return errors.Errorf("%s - proposal response: %v", err, proposalResp) 144 } 145 146 if invoke { 147 logger.Debugf("ESCC invoke result: %v", proposalResp) 148 pRespPayload, err := protoutil.UnmarshalProposalResponsePayload(proposalResp.Payload) 149 if err != nil { 150 return errors.WithMessage(err, "error while unmarshalling proposal response payload") 151 } 152 ca, err := protoutil.UnmarshalChaincodeAction(pRespPayload.Extension) 153 if err != nil { 154 return errors.WithMessage(err, "error while unmarshalling chaincode action") 155 } 156 if proposalResp.Endorsement == nil { 157 return errors.Errorf("endorsement failure during invoke. response: %v", proposalResp.Response) 158 } 159 logger.Infof("Chaincode invoke successful. result: %v", ca.Response) 160 } else { 161 if proposalResp == nil { 162 return errors.New("error during query: received nil proposal response") 163 } 164 if proposalResp.Endorsement == nil { 165 return errors.Errorf("endorsement failure during query. response: %v", proposalResp.Response) 166 } 167 168 if chaincodeQueryRaw && chaincodeQueryHex { 169 return fmt.Errorf("options --raw (-r) and --hex (-x) are not compatible") 170 } 171 if chaincodeQueryRaw { 172 fmt.Println(proposalResp.Response.Payload) 173 return nil 174 } 175 if chaincodeQueryHex { 176 fmt.Printf("%x\n", proposalResp.Response.Payload) 177 return nil 178 } 179 fmt.Println(string(proposalResp.Response.Payload)) 180 } 181 return nil 182 } 183 184 type endorsementPolicy struct { 185 ChannelConfigPolicy string `json:"channelConfigPolicy,omitempty"` 186 SignaturePolicy string `json:"signaturePolicy,omitempty"` 187 } 188 189 type collectionConfigJson struct { 190 Name string `json:"name"` 191 Policy string `json:"policy"` 192 RequiredPeerCount *int32 `json:"requiredPeerCount"` 193 MaxPeerCount *int32 `json:"maxPeerCount"` 194 BlockToLive uint64 `json:"blockToLive"` 195 MemberOnlyRead bool `json:"memberOnlyRead"` 196 MemberOnlyWrite bool `json:"memberOnlyWrite"` 197 EndorsementPolicy *endorsementPolicy `json:"endorsementPolicy,omitempty"` 198 } 199 200 // GetCollectionConfigFromFile retrieves the collection configuration 201 // from the supplied file; the supplied file must contain a 202 // json-formatted array of collectionConfigJson elements 203 func GetCollectionConfigFromFile(ccFile string) (*pb.CollectionConfigPackage, []byte, error) { 204 fileBytes, err := ioutil.ReadFile(ccFile) 205 if err != nil { 206 return nil, nil, errors.Wrapf(err, "could not read file '%s'", ccFile) 207 } 208 209 return getCollectionConfigFromBytes(fileBytes) 210 } 211 212 // getCollectionConfig retrieves the collection configuration 213 // from the supplied byte array; the byte array must contain a 214 // json-formatted array of collectionConfigJson elements 215 func getCollectionConfigFromBytes(cconfBytes []byte) (*pb.CollectionConfigPackage, []byte, error) { 216 cconf := &[]collectionConfigJson{} 217 err := json.Unmarshal(cconfBytes, cconf) 218 if err != nil { 219 return nil, nil, errors.Wrap(err, "could not parse the collection configuration") 220 } 221 222 ccarray := make([]*pb.CollectionConfig, 0, len(*cconf)) 223 for _, cconfitem := range *cconf { 224 p, err := policydsl.FromString(cconfitem.Policy) 225 if err != nil { 226 return nil, nil, errors.WithMessagef(err, "invalid policy %s", cconfitem.Policy) 227 } 228 229 cpc := &pb.CollectionPolicyConfig{ 230 Payload: &pb.CollectionPolicyConfig_SignaturePolicy{ 231 SignaturePolicy: p, 232 }, 233 } 234 235 var ep *pb.ApplicationPolicy 236 if cconfitem.EndorsementPolicy != nil { 237 signaturePolicy := cconfitem.EndorsementPolicy.SignaturePolicy 238 channelConfigPolicy := cconfitem.EndorsementPolicy.ChannelConfigPolicy 239 ep, err = getApplicationPolicy(signaturePolicy, channelConfigPolicy) 240 if err != nil { 241 return nil, nil, errors.WithMessagef(err, "invalid endorsement policy [%#v]", cconfitem.EndorsementPolicy) 242 } 243 } 244 245 // Set default requiredPeerCount and MaxPeerCount if not specified in json 246 requiredPeerCount := int32(0) 247 maxPeerCount := int32(1) 248 if cconfitem.RequiredPeerCount != nil { 249 requiredPeerCount = *cconfitem.RequiredPeerCount 250 } 251 if cconfitem.MaxPeerCount != nil { 252 maxPeerCount = *cconfitem.MaxPeerCount 253 } 254 255 cc := &pb.CollectionConfig{ 256 Payload: &pb.CollectionConfig_StaticCollectionConfig{ 257 StaticCollectionConfig: &pb.StaticCollectionConfig{ 258 Name: cconfitem.Name, 259 MemberOrgsPolicy: cpc, 260 RequiredPeerCount: requiredPeerCount, 261 MaximumPeerCount: maxPeerCount, 262 BlockToLive: cconfitem.BlockToLive, 263 MemberOnlyRead: cconfitem.MemberOnlyRead, 264 MemberOnlyWrite: cconfitem.MemberOnlyWrite, 265 EndorsementPolicy: ep, 266 }, 267 }, 268 } 269 270 ccarray = append(ccarray, cc) 271 } 272 273 ccp := &pb.CollectionConfigPackage{Config: ccarray} 274 ccpBytes, err := proto.Marshal(ccp) 275 return ccp, ccpBytes, err 276 } 277 278 func getApplicationPolicy(signaturePolicy, channelConfigPolicy string) (*pb.ApplicationPolicy, error) { 279 if signaturePolicy == "" && channelConfigPolicy == "" { 280 // no policy, no problem 281 return nil, nil 282 } 283 284 if signaturePolicy != "" && channelConfigPolicy != "" { 285 // mo policies, mo problems 286 return nil, errors.New(`cannot specify both "--signature-policy" and "--channel-config-policy"`) 287 } 288 289 var applicationPolicy *pb.ApplicationPolicy 290 if signaturePolicy != "" { 291 signaturePolicyEnvelope, err := policydsl.FromString(signaturePolicy) 292 if err != nil { 293 return nil, errors.Errorf("invalid signature policy: %s", signaturePolicy) 294 } 295 296 applicationPolicy = &pb.ApplicationPolicy{ 297 Type: &pb.ApplicationPolicy_SignaturePolicy{ 298 SignaturePolicy: signaturePolicyEnvelope, 299 }, 300 } 301 } 302 303 if channelConfigPolicy != "" { 304 applicationPolicy = &pb.ApplicationPolicy{ 305 Type: &pb.ApplicationPolicy_ChannelConfigPolicyReference{ 306 ChannelConfigPolicyReference: channelConfigPolicy, 307 }, 308 } 309 } 310 311 return applicationPolicy, nil 312 } 313 314 func checkChaincodeCmdParams(cmd *cobra.Command) error { 315 // we need chaincode name for everything, including deploy 316 if chaincodeName == common.UndefinedParamValue { 317 return errors.Errorf("must supply value for %s name parameter", chainFuncName) 318 } 319 320 if cmd.Name() == instantiateCmdName || cmd.Name() == installCmdName || 321 cmd.Name() == upgradeCmdName || cmd.Name() == packageCmdName { 322 if chaincodeVersion == common.UndefinedParamValue { 323 return errors.Errorf("chaincode version is not provided for %s", cmd.Name()) 324 } 325 326 if escc != common.UndefinedParamValue { 327 logger.Infof("Using escc %s", escc) 328 } else { 329 logger.Info("Using default escc") 330 escc = "escc" 331 } 332 333 if vscc != common.UndefinedParamValue { 334 logger.Infof("Using vscc %s", vscc) 335 } else { 336 logger.Info("Using default vscc") 337 vscc = "vscc" 338 } 339 340 if policy != common.UndefinedParamValue { 341 p, err := policydsl.FromString(policy) 342 if err != nil { 343 return errors.Errorf("invalid policy %s", policy) 344 } 345 policyMarshalled = protoutil.MarshalOrPanic(p) 346 } 347 348 if collectionsConfigFile != common.UndefinedParamValue { 349 var err error 350 _, collectionConfigBytes, err = GetCollectionConfigFromFile(collectionsConfigFile) 351 if err != nil { 352 return errors.WithMessagef(err, "invalid collection configuration in file %s", collectionsConfigFile) 353 } 354 } 355 } 356 357 // Check that non-empty chaincode parameters contain only Args as a key. 358 // Type checking is done later when the JSON is actually unmarshaled 359 // into a pb.ChaincodeInput. To better understand what's going 360 // on here with JSON parsing see http://blog.golang.org/json-and-go - 361 // Generic JSON with interface{} 362 if chaincodeCtorJSON != "{}" { 363 var f interface{} 364 err := json.Unmarshal([]byte(chaincodeCtorJSON), &f) 365 if err != nil { 366 return errors.Wrap(err, "chaincode argument error") 367 } 368 m := f.(map[string]interface{}) 369 sm := make(map[string]interface{}) 370 for k := range m { 371 sm[strings.ToLower(k)] = m[k] 372 } 373 _, argsPresent := sm["args"] 374 _, funcPresent := sm["function"] 375 if !argsPresent || (len(m) == 2 && !funcPresent) || len(m) > 2 { 376 return errors.New("non-empty JSON chaincode parameters must contain the following keys: 'Args' or 'Function' and 'Args'") 377 } 378 } else { 379 if cmd == nil || (cmd != chaincodeInstallCmd && cmd != chaincodePackageCmd) { 380 return errors.New("empty JSON chaincode parameters must contain the following keys: 'Args' or 'Function' and 'Args'") 381 } 382 } 383 384 return nil 385 } 386 387 func validatePeerConnectionParameters(cmdName string) error { 388 if connectionProfile != common.UndefinedParamValue { 389 networkConfig, err := common.GetConfig(connectionProfile) 390 if err != nil { 391 return err 392 } 393 if len(networkConfig.Channels[channelID].Peers) != 0 { 394 peerAddresses = []string{} 395 tlsRootCertFiles = []string{} 396 for peer, peerChannelConfig := range networkConfig.Channels[channelID].Peers { 397 if peerChannelConfig.EndorsingPeer { 398 peerConfig, ok := networkConfig.Peers[peer] 399 if !ok { 400 return errors.Errorf("peer '%s' is defined in the channel config but doesn't have associated peer config", peer) 401 } 402 peerAddresses = append(peerAddresses, peerConfig.URL) 403 tlsRootCertFiles = append(tlsRootCertFiles, peerConfig.TLSCACerts.Path) 404 } 405 } 406 } 407 } 408 409 // currently only support multiple peer addresses for invoke 410 multiplePeersAllowed := map[string]bool{ 411 "invoke": true, 412 } 413 _, ok := multiplePeersAllowed[cmdName] 414 if !ok && len(peerAddresses) > 1 { 415 return errors.Errorf("'%s' command can only be executed against one peer. received %d", cmdName, len(peerAddresses)) 416 } 417 418 if len(tlsRootCertFiles) > len(peerAddresses) { 419 logger.Warningf("received more TLS root cert files (%d) than peer addresses (%d)", len(tlsRootCertFiles), len(peerAddresses)) 420 } 421 422 if viper.GetBool("peer.tls.enabled") { 423 if len(tlsRootCertFiles) != len(peerAddresses) { 424 return errors.Errorf("number of peer addresses (%d) does not match the number of TLS root cert files (%d)", len(peerAddresses), len(tlsRootCertFiles)) 425 } 426 } else { 427 tlsRootCertFiles = nil 428 } 429 430 return nil 431 } 432 433 // ChaincodeCmdFactory holds the clients used by ChaincodeCmd 434 type ChaincodeCmdFactory struct { 435 EndorserClients []pb.EndorserClient 436 DeliverClients []pb.DeliverClient 437 Certificate tls.Certificate 438 Signer identity.SignerSerializer 439 BroadcastClient common.BroadcastClient 440 } 441 442 // InitCmdFactory init the ChaincodeCmdFactory with default clients 443 func InitCmdFactory(cmdName string, isEndorserRequired, isOrdererRequired bool, cryptoProvider bccsp.BCCSP) (*ChaincodeCmdFactory, error) { 444 var err error 445 var endorserClients []pb.EndorserClient 446 var deliverClients []pb.DeliverClient 447 if isEndorserRequired { 448 if err = validatePeerConnectionParameters(cmdName); err != nil { 449 return nil, errors.WithMessage(err, "error validating peer connection parameters") 450 } 451 for i, address := range peerAddresses { 452 var tlsRootCertFile string 453 if tlsRootCertFiles != nil { 454 tlsRootCertFile = tlsRootCertFiles[i] 455 } 456 endorserClient, err := common.GetEndorserClientFnc(address, tlsRootCertFile) 457 if err != nil { 458 return nil, errors.WithMessagef(err, "error getting endorser client for %s", cmdName) 459 } 460 endorserClients = append(endorserClients, endorserClient) 461 deliverClient, err := common.GetPeerDeliverClientFnc(address, tlsRootCertFile) 462 if err != nil { 463 return nil, errors.WithMessagef(err, "error getting deliver client for %s", cmdName) 464 } 465 deliverClients = append(deliverClients, deliverClient) 466 } 467 if len(endorserClients) == 0 { 468 return nil, errors.New("no endorser clients retrieved - this might indicate a bug") 469 } 470 } 471 certificate, err := common.GetClientCertificateFnc() 472 if err != nil { 473 return nil, errors.WithMessage(err, "error getting client certificate") 474 } 475 476 signer, err := common.GetDefaultSignerFnc() 477 if err != nil { 478 return nil, errors.WithMessage(err, "error getting default signer") 479 } 480 481 var broadcastClient common.BroadcastClient 482 if isOrdererRequired { 483 if len(common.OrderingEndpoint) == 0 { 484 if len(endorserClients) == 0 { 485 return nil, errors.New("orderer is required, but no ordering endpoint or endorser client supplied") 486 } 487 endorserClient := endorserClients[0] 488 489 orderingEndpoints, err := common.GetOrdererEndpointOfChainFnc(channelID, signer, endorserClient, cryptoProvider) 490 if err != nil { 491 return nil, errors.WithMessagef(err, "error getting channel (%s) orderer endpoint", channelID) 492 } 493 if len(orderingEndpoints) == 0 { 494 return nil, errors.Errorf("no orderer endpoints retrieved for channel %s, pass orderer endpoint with -o flag instead", channelID) 495 } 496 logger.Infof("Retrieved channel (%s) orderer endpoint: %s", channelID, orderingEndpoints[0]) 497 // override viper env 498 viper.Set("orderer.address", orderingEndpoints[0]) 499 } 500 501 broadcastClient, err = common.GetBroadcastClientFnc() 502 if err != nil { 503 return nil, errors.WithMessage(err, "error getting broadcast client") 504 } 505 } 506 return &ChaincodeCmdFactory{ 507 EndorserClients: endorserClients, 508 DeliverClients: deliverClients, 509 Signer: signer, 510 BroadcastClient: broadcastClient, 511 Certificate: certificate, 512 }, nil 513 } 514 515 // processProposals sends a signed proposal to a set of peers, and gathers all the responses. 516 func processProposals(endorserClients []pb.EndorserClient, signedProposal *pb.SignedProposal) ([]*pb.ProposalResponse, error) { 517 responsesCh := make(chan *pb.ProposalResponse, len(endorserClients)) 518 errorCh := make(chan error, len(endorserClients)) 519 wg := sync.WaitGroup{} 520 for _, endorser := range endorserClients { 521 wg.Add(1) 522 go func(endorser pb.EndorserClient) { 523 defer wg.Done() 524 proposalResp, err := endorser.ProcessProposal(context.Background(), signedProposal) 525 if err != nil { 526 errorCh <- err 527 return 528 } 529 responsesCh <- proposalResp 530 }(endorser) 531 } 532 wg.Wait() 533 close(responsesCh) 534 close(errorCh) 535 for err := range errorCh { 536 return nil, err 537 } 538 var responses []*pb.ProposalResponse 539 for response := range responsesCh { 540 responses = append(responses, response) 541 } 542 return responses, nil 543 } 544 545 // ChaincodeInvokeOrQuery invokes or queries the chaincode. If successful, the 546 // INVOKE form prints the ProposalResponse to STDOUT, and the QUERY form prints 547 // the query result on STDOUT. A command-line flag (-r, --raw) determines 548 // whether the query result is output as raw bytes, or as a printable string. 549 // The printable form is optionally (-x, --hex) a hexadecimal representation 550 // of the query response. If the query response is NIL, nothing is output. 551 // 552 // NOTE - Query will likely go away as all interactions with the endorser are 553 // Proposal and ProposalResponses 554 func ChaincodeInvokeOrQuery( 555 spec *pb.ChaincodeSpec, 556 cID string, 557 txID string, 558 invoke bool, 559 signer identity.SignerSerializer, 560 certificate tls.Certificate, 561 endorserClients []pb.EndorserClient, 562 deliverClients []pb.DeliverClient, 563 bc common.BroadcastClient, 564 ) (*pb.ProposalResponse, error) { 565 // Build the ChaincodeInvocationSpec message 566 invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} 567 568 creator, err := signer.Serialize() 569 if err != nil { 570 return nil, errors.WithMessage(err, "error serializing identity") 571 } 572 573 funcName := "invoke" 574 if !invoke { 575 funcName = "query" 576 } 577 578 // extract the transient field if it exists 579 var tMap map[string][]byte 580 if transient != "" { 581 if err := json.Unmarshal([]byte(transient), &tMap); err != nil { 582 return nil, errors.Wrap(err, "error parsing transient string") 583 } 584 } 585 586 prop, txid, err := protoutil.CreateChaincodeProposalWithTxIDAndTransient(pcommon.HeaderType_ENDORSER_TRANSACTION, cID, invocation, creator, txID, tMap) 587 if err != nil { 588 return nil, errors.WithMessagef(err, "error creating proposal for %s", funcName) 589 } 590 591 signedProp, err := protoutil.GetSignedProposal(prop, signer) 592 if err != nil { 593 return nil, errors.WithMessagef(err, "error creating signed proposal for %s", funcName) 594 } 595 596 responses, err := processProposals(endorserClients, signedProp) 597 if err != nil { 598 return nil, errors.WithMessagef(err, "error endorsing %s", funcName) 599 } 600 601 if len(responses) == 0 { 602 // this should only happen if some new code has introduced a bug 603 return nil, errors.New("no proposal responses received - this might indicate a bug") 604 } 605 // all responses will be checked when the signed transaction is created. 606 // for now, just set this so we check the first response's status 607 proposalResp := responses[0] 608 609 if invoke { 610 if proposalResp != nil { 611 if proposalResp.Response.Status >= shim.ERRORTHRESHOLD { 612 return proposalResp, nil 613 } 614 // assemble a signed transaction (it's an Envelope message) 615 env, err := protoutil.CreateSignedTx(prop, signer, responses...) 616 if err != nil { 617 return proposalResp, errors.WithMessage(err, "could not assemble transaction") 618 } 619 var dg *DeliverGroup 620 var ctx context.Context 621 if waitForEvent { 622 var cancelFunc context.CancelFunc 623 ctx, cancelFunc = context.WithTimeout(context.Background(), waitForEventTimeout) 624 defer cancelFunc() 625 626 dg = NewDeliverGroup( 627 deliverClients, 628 peerAddresses, 629 signer, 630 certificate, 631 channelID, 632 txid, 633 ) 634 // connect to deliver service on all peers 635 err := dg.Connect(ctx) 636 if err != nil { 637 return nil, err 638 } 639 } 640 641 // send the envelope for ordering 642 if err = bc.Send(env); err != nil { 643 return proposalResp, errors.WithMessagef(err, "error sending transaction for %s", funcName) 644 } 645 646 if dg != nil && ctx != nil { 647 // wait for event that contains the txid from all peers 648 err = dg.Wait(ctx) 649 if err != nil { 650 return nil, err 651 } 652 } 653 } 654 } 655 656 return proposalResp, nil 657 } 658 659 // DeliverGroup holds all of the information needed to connect 660 // to a set of peers to wait for the interested txid to be 661 // committed to the ledgers of all peers. This functionality 662 // is currently implemented via the peer's DeliverFiltered service. 663 // An error from any of the peers/deliver clients will result in 664 // the invoke command returning an error. Only the first error that 665 // occurs will be set 666 type DeliverGroup struct { 667 Clients []*DeliverClient 668 Certificate tls.Certificate 669 ChannelID string 670 TxID string 671 Signer identity.SignerSerializer 672 mutex sync.Mutex 673 Error error 674 wg sync.WaitGroup 675 } 676 677 // DeliverClient holds the client/connection related to a specific 678 // peer. The address is included for logging purposes 679 type DeliverClient struct { 680 Client pb.DeliverClient 681 Connection pb.Deliver_DeliverClient 682 Address string 683 } 684 685 func NewDeliverGroup( 686 deliverClients []pb.DeliverClient, 687 peerAddresses []string, 688 signer identity.SignerSerializer, 689 certificate tls.Certificate, 690 channelID string, 691 txid string, 692 ) *DeliverGroup { 693 clients := make([]*DeliverClient, len(deliverClients)) 694 for i, client := range deliverClients { 695 address := peerAddresses[i] 696 if address == "" { 697 address = viper.GetString("peer.address") 698 } 699 dc := &DeliverClient{ 700 Client: client, 701 Address: address, 702 } 703 clients[i] = dc 704 } 705 706 dg := &DeliverGroup{ 707 Clients: clients, 708 Certificate: certificate, 709 ChannelID: channelID, 710 TxID: txid, 711 Signer: signer, 712 } 713 714 return dg 715 } 716 717 // Connect waits for all deliver clients in the group to connect to 718 // the peer's deliver service, receive an error, or for the context 719 // to timeout. An error will be returned whenever even a single 720 // deliver client fails to connect to its peer 721 func (dg *DeliverGroup) Connect(ctx context.Context) error { 722 dg.wg.Add(len(dg.Clients)) 723 for _, client := range dg.Clients { 724 go dg.ClientConnect(ctx, client) 725 } 726 readyCh := make(chan struct{}) 727 go dg.WaitForWG(readyCh) 728 729 select { 730 case <-readyCh: 731 if dg.Error != nil { 732 err := errors.WithMessage(dg.Error, "failed to connect to deliver on all peers") 733 return err 734 } 735 case <-ctx.Done(): 736 err := errors.New("timed out waiting for connection to deliver on all peers") 737 return err 738 } 739 740 return nil 741 } 742 743 // ClientConnect sends a deliver seek info envelope using the 744 // provided deliver client, setting the deliverGroup's Error 745 // field upon any error 746 func (dg *DeliverGroup) ClientConnect(ctx context.Context, dc *DeliverClient) { 747 defer dg.wg.Done() 748 df, err := dc.Client.DeliverFiltered(ctx) 749 if err != nil { 750 err = errors.WithMessagef(err, "error connecting to deliver filtered at %s", dc.Address) 751 dg.setError(err) 752 return 753 } 754 defer df.CloseSend() 755 dc.Connection = df 756 757 envelope := createDeliverEnvelope(dg.ChannelID, dg.Certificate, dg.Signer) 758 err = df.Send(envelope) 759 if err != nil { 760 err = errors.WithMessagef(err, "error sending deliver seek info envelope to %s", dc.Address) 761 dg.setError(err) 762 return 763 } 764 } 765 766 // Wait waits for all deliver client connections in the group to 767 // either receive a block with the txid, an error, or for the 768 // context to timeout 769 func (dg *DeliverGroup) Wait(ctx context.Context) error { 770 if len(dg.Clients) == 0 { 771 return nil 772 } 773 774 dg.wg.Add(len(dg.Clients)) 775 for _, client := range dg.Clients { 776 go dg.ClientWait(client) 777 } 778 readyCh := make(chan struct{}) 779 go dg.WaitForWG(readyCh) 780 781 select { 782 case <-readyCh: 783 if dg.Error != nil { 784 return dg.Error 785 } 786 case <-ctx.Done(): 787 err := errors.New("timed out waiting for txid on all peers") 788 return err 789 } 790 791 return nil 792 } 793 794 // ClientWait waits for the specified deliver client to receive 795 // a block event with the requested txid 796 func (dg *DeliverGroup) ClientWait(dc *DeliverClient) { 797 defer dg.wg.Done() 798 for { 799 resp, err := dc.Connection.Recv() 800 if err != nil { 801 err = errors.WithMessagef(err, "error receiving from deliver filtered at %s", dc.Address) 802 dg.setError(err) 803 return 804 } 805 switch r := resp.Type.(type) { 806 case *pb.DeliverResponse_FilteredBlock: 807 filteredTransactions := r.FilteredBlock.FilteredTransactions 808 for _, tx := range filteredTransactions { 809 if tx.Txid == dg.TxID { 810 logger.Infof("txid [%s] committed with status (%s) at %s", dg.TxID, tx.TxValidationCode, dc.Address) 811 if tx.TxValidationCode != pb.TxValidationCode_VALID { 812 err = errors.Errorf("transaction invalidated with status (%s)", tx.TxValidationCode) 813 dg.setError(err) 814 } 815 return 816 } 817 } 818 case *pb.DeliverResponse_Status: 819 err = errors.Errorf("deliver completed with status (%s) before txid received", r.Status) 820 dg.setError(err) 821 return 822 default: 823 err = errors.Errorf("received unexpected response type (%T) from %s", r, dc.Address) 824 dg.setError(err) 825 return 826 } 827 } 828 } 829 830 // WaitForWG waits for the deliverGroup's wait group and closes 831 // the channel when ready 832 func (dg *DeliverGroup) WaitForWG(readyCh chan struct{}) { 833 dg.wg.Wait() 834 close(readyCh) 835 } 836 837 // setError serializes an error for the deliverGroup 838 func (dg *DeliverGroup) setError(err error) { 839 dg.mutex.Lock() 840 dg.Error = err 841 dg.mutex.Unlock() 842 } 843 844 func createDeliverEnvelope( 845 channelID string, 846 certificate tls.Certificate, 847 signer identity.SignerSerializer, 848 ) *pcommon.Envelope { 849 var tlsCertHash []byte 850 // check for client certificate and create hash if present 851 if len(certificate.Certificate) > 0 { 852 tlsCertHash = util.ComputeSHA256(certificate.Certificate[0]) 853 } 854 855 start := &ab.SeekPosition{ 856 Type: &ab.SeekPosition_Newest{ 857 Newest: &ab.SeekNewest{}, 858 }, 859 } 860 861 stop := &ab.SeekPosition{ 862 Type: &ab.SeekPosition_Specified{ 863 Specified: &ab.SeekSpecified{ 864 Number: math.MaxUint64, 865 }, 866 }, 867 } 868 869 seekInfo := &ab.SeekInfo{ 870 Start: start, 871 Stop: stop, 872 Behavior: ab.SeekInfo_BLOCK_UNTIL_READY, 873 } 874 875 env, err := protoutil.CreateSignedEnvelopeWithTLSBinding( 876 pcommon.HeaderType_DELIVER_SEEK_INFO, 877 channelID, 878 signer, 879 seekInfo, 880 int32(0), 881 uint64(0), 882 tlsCertHash, 883 ) 884 if err != nil { 885 logger.Errorf("Error signing envelope: %s", err) 886 return nil 887 } 888 889 return env 890 }