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