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