github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/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/osdi23p228/fabric/bccsp" 25 "github.com/osdi23p228/fabric/common/policydsl" 26 "github.com/osdi23p228/fabric/common/util" 27 "github.com/osdi23p228/fabric/internal/peer/common" 28 "github.com/osdi23p228/fabric/internal/pkg/identity" 29 "github.com/osdi23p228/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, pass orderer endpoint with -o flag instead", 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 if err != nil { 504 return nil, errors.WithMessage(err, "error getting broadcast client") 505 } 506 } 507 return &ChaincodeCmdFactory{ 508 EndorserClients: endorserClients, 509 DeliverClients: deliverClients, 510 Signer: signer, 511 BroadcastClient: broadcastClient, 512 Certificate: certificate, 513 }, nil 514 } 515 516 // processProposals sends a signed proposal to a set of peers, and gathers all the responses. 517 func processProposals(endorserClients []pb.EndorserClient, signedProposal *pb.SignedProposal) ([]*pb.ProposalResponse, error) { 518 responsesCh := make(chan *pb.ProposalResponse, len(endorserClients)) 519 errorCh := make(chan error, len(endorserClients)) 520 wg := sync.WaitGroup{} 521 for _, endorser := range endorserClients { 522 wg.Add(1) 523 go func(endorser pb.EndorserClient) { 524 defer wg.Done() 525 proposalResp, err := endorser.ProcessProposal(context.Background(), signedProposal) 526 if err != nil { 527 errorCh <- err 528 return 529 } 530 responsesCh <- proposalResp 531 }(endorser) 532 } 533 wg.Wait() 534 close(responsesCh) 535 close(errorCh) 536 for err := range errorCh { 537 return nil, err 538 } 539 var responses []*pb.ProposalResponse 540 for response := range responsesCh { 541 responses = append(responses, response) 542 } 543 return responses, nil 544 } 545 546 // ChaincodeInvokeOrQuery invokes or queries the chaincode. If successful, the 547 // INVOKE form prints the ProposalResponse to STDOUT, and the QUERY form prints 548 // the query result on STDOUT. A command-line flag (-r, --raw) determines 549 // whether the query result is output as raw bytes, or as a printable string. 550 // The printable form is optionally (-x, --hex) a hexadecimal representation 551 // of the query response. If the query response is NIL, nothing is output. 552 // 553 // NOTE - Query will likely go away as all interactions with the endorser are 554 // Proposal and ProposalResponses 555 func ChaincodeInvokeOrQuery( 556 spec *pb.ChaincodeSpec, 557 cID string, 558 txID string, 559 invoke bool, 560 signer identity.SignerSerializer, 561 certificate tls.Certificate, 562 endorserClients []pb.EndorserClient, 563 deliverClients []pb.DeliverClient, 564 bc common.BroadcastClient, 565 ) (*pb.ProposalResponse, error) { 566 // Build the ChaincodeInvocationSpec message 567 invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} 568 569 creator, err := signer.Serialize() 570 if err != nil { 571 return nil, errors.WithMessage(err, "error serializing identity") 572 } 573 574 funcName := "invoke" 575 if !invoke { 576 funcName = "query" 577 } 578 579 // extract the transient field if it exists 580 var tMap map[string][]byte 581 if transient != "" { 582 if err := json.Unmarshal([]byte(transient), &tMap); err != nil { 583 return nil, errors.Wrap(err, "error parsing transient string") 584 } 585 } 586 587 prop, txid, err := protoutil.CreateChaincodeProposalWithTxIDAndTransient(pcommon.HeaderType_ENDORSER_TRANSACTION, cID, invocation, creator, txID, tMap) 588 if err != nil { 589 return nil, errors.WithMessagef(err, "error creating proposal for %s", funcName) 590 } 591 592 signedProp, err := protoutil.GetSignedProposal(prop, signer) 593 if err != nil { 594 return nil, errors.WithMessagef(err, "error creating signed proposal for %s", funcName) 595 } 596 597 responses, err := processProposals(endorserClients, signedProp) 598 if err != nil { 599 return nil, errors.WithMessagef(err, "error endorsing %s", funcName) 600 } 601 602 if len(responses) == 0 { 603 // this should only happen if some new code has introduced a bug 604 return nil, errors.New("no proposal responses received - this might indicate a bug") 605 } 606 // all responses will be checked when the signed transaction is created. 607 // for now, just set this so we check the first response's status 608 proposalResp := responses[0] 609 610 if invoke { 611 if proposalResp != nil { 612 if proposalResp.Response.Status >= shim.ERRORTHRESHOLD { 613 return proposalResp, nil 614 } 615 // assemble a signed transaction (it's an Envelope message) 616 env, err := protoutil.CreateSignedTx(prop, signer, responses...) 617 if err != nil { 618 return proposalResp, errors.WithMessage(err, "could not assemble transaction") 619 } 620 var dg *DeliverGroup 621 var ctx context.Context 622 if waitForEvent { 623 var cancelFunc context.CancelFunc 624 ctx, cancelFunc = context.WithTimeout(context.Background(), waitForEventTimeout) 625 defer cancelFunc() 626 627 dg = NewDeliverGroup( 628 deliverClients, 629 peerAddresses, 630 signer, 631 certificate, 632 channelID, 633 txid, 634 ) 635 // connect to deliver service on all peers 636 err := dg.Connect(ctx) 637 if err != nil { 638 return nil, err 639 } 640 } 641 642 // send the envelope for ordering 643 if err = bc.Send(env); err != nil { 644 return proposalResp, errors.WithMessagef(err, "error sending transaction for %s", funcName) 645 } 646 647 if dg != nil && ctx != nil { 648 // wait for event that contains the txid from all peers 649 err = dg.Wait(ctx) 650 if err != nil { 651 return nil, err 652 } 653 } 654 } 655 } 656 657 return proposalResp, nil 658 } 659 660 // DeliverGroup holds all of the information needed to connect 661 // to a set of peers to wait for the interested txid to be 662 // committed to the ledgers of all peers. This functionality 663 // is currently implemented via the peer's DeliverFiltered service. 664 // An error from any of the peers/deliver clients will result in 665 // the invoke command returning an error. Only the first error that 666 // occurs will be set 667 type DeliverGroup struct { 668 Clients []*DeliverClient 669 Certificate tls.Certificate 670 ChannelID string 671 TxID string 672 Signer identity.SignerSerializer 673 mutex sync.Mutex 674 Error error 675 wg sync.WaitGroup 676 } 677 678 // DeliverClient holds the client/connection related to a specific 679 // peer. The address is included for logging purposes 680 type DeliverClient struct { 681 Client pb.DeliverClient 682 Connection pb.Deliver_DeliverClient 683 Address string 684 } 685 686 func NewDeliverGroup( 687 deliverClients []pb.DeliverClient, 688 peerAddresses []string, 689 signer identity.SignerSerializer, 690 certificate tls.Certificate, 691 channelID string, 692 txid string, 693 ) *DeliverGroup { 694 clients := make([]*DeliverClient, len(deliverClients)) 695 for i, client := range deliverClients { 696 address := peerAddresses[i] 697 if address == "" { 698 address = viper.GetString("peer.address") 699 } 700 dc := &DeliverClient{ 701 Client: client, 702 Address: address, 703 } 704 clients[i] = dc 705 } 706 707 dg := &DeliverGroup{ 708 Clients: clients, 709 Certificate: certificate, 710 ChannelID: channelID, 711 TxID: txid, 712 Signer: signer, 713 } 714 715 return dg 716 } 717 718 // Connect waits for all deliver clients in the group to connect to 719 // the peer's deliver service, receive an error, or for the context 720 // to timeout. An error will be returned whenever even a single 721 // deliver client fails to connect to its peer 722 func (dg *DeliverGroup) Connect(ctx context.Context) error { 723 dg.wg.Add(len(dg.Clients)) 724 for _, client := range dg.Clients { 725 go dg.ClientConnect(ctx, client) 726 } 727 readyCh := make(chan struct{}) 728 go dg.WaitForWG(readyCh) 729 730 select { 731 case <-readyCh: 732 if dg.Error != nil { 733 err := errors.WithMessage(dg.Error, "failed to connect to deliver on all peers") 734 return err 735 } 736 case <-ctx.Done(): 737 err := errors.New("timed out waiting for connection to deliver on all peers") 738 return err 739 } 740 741 return nil 742 } 743 744 // ClientConnect sends a deliver seek info envelope using the 745 // provided deliver client, setting the deliverGroup's Error 746 // field upon any error 747 func (dg *DeliverGroup) ClientConnect(ctx context.Context, dc *DeliverClient) { 748 defer dg.wg.Done() 749 df, err := dc.Client.DeliverFiltered(ctx) 750 if err != nil { 751 err = errors.WithMessagef(err, "error connecting to deliver filtered at %s", dc.Address) 752 dg.setError(err) 753 return 754 } 755 defer df.CloseSend() 756 dc.Connection = df 757 758 envelope := createDeliverEnvelope(dg.ChannelID, dg.Certificate, dg.Signer) 759 err = df.Send(envelope) 760 if err != nil { 761 err = errors.WithMessagef(err, "error sending deliver seek info envelope to %s", dc.Address) 762 dg.setError(err) 763 return 764 } 765 } 766 767 // Wait waits for all deliver client connections in the group to 768 // either receive a block with the txid, an error, or for the 769 // context to timeout 770 func (dg *DeliverGroup) Wait(ctx context.Context) error { 771 if len(dg.Clients) == 0 { 772 return nil 773 } 774 775 dg.wg.Add(len(dg.Clients)) 776 for _, client := range dg.Clients { 777 go dg.ClientWait(client) 778 } 779 readyCh := make(chan struct{}) 780 go dg.WaitForWG(readyCh) 781 782 select { 783 case <-readyCh: 784 if dg.Error != nil { 785 return dg.Error 786 } 787 case <-ctx.Done(): 788 err := errors.New("timed out waiting for txid on all peers") 789 return err 790 } 791 792 return nil 793 } 794 795 // ClientWait waits for the specified deliver client to receive 796 // a block event with the requested txid 797 func (dg *DeliverGroup) ClientWait(dc *DeliverClient) { 798 defer dg.wg.Done() 799 for { 800 resp, err := dc.Connection.Recv() 801 if err != nil { 802 err = errors.WithMessagef(err, "error receiving from deliver filtered at %s", dc.Address) 803 dg.setError(err) 804 return 805 } 806 switch r := resp.Type.(type) { 807 case *pb.DeliverResponse_FilteredBlock: 808 filteredTransactions := r.FilteredBlock.FilteredTransactions 809 for _, tx := range filteredTransactions { 810 if tx.Txid == dg.TxID { 811 logger.Infof("txid [%s] committed with status (%s) at %s", dg.TxID, tx.TxValidationCode, dc.Address) 812 if tx.TxValidationCode != pb.TxValidationCode_VALID { 813 err = errors.Errorf("transaction invalidated with status (%s)", tx.TxValidationCode) 814 dg.setError(err) 815 } 816 return 817 } 818 } 819 case *pb.DeliverResponse_Status: 820 err = errors.Errorf("deliver completed with status (%s) before txid received", r.Status) 821 dg.setError(err) 822 return 823 default: 824 err = errors.Errorf("received unexpected response type (%T) from %s", r, dc.Address) 825 dg.setError(err) 826 return 827 } 828 } 829 } 830 831 // WaitForWG waits for the deliverGroup's wait group and closes 832 // the channel when ready 833 func (dg *DeliverGroup) WaitForWG(readyCh chan struct{}) { 834 dg.wg.Wait() 835 close(readyCh) 836 } 837 838 // setError serializes an error for the deliverGroup 839 func (dg *DeliverGroup) setError(err error) { 840 dg.mutex.Lock() 841 dg.Error = err 842 dg.mutex.Unlock() 843 } 844 845 func createDeliverEnvelope( 846 channelID string, 847 certificate tls.Certificate, 848 signer identity.SignerSerializer, 849 ) *pcommon.Envelope { 850 var tlsCertHash []byte 851 // check for client certificate and create hash if present 852 if len(certificate.Certificate) > 0 { 853 tlsCertHash = util.ComputeSHA256(certificate.Certificate[0]) 854 } 855 856 start := &ab.SeekPosition{ 857 Type: &ab.SeekPosition_Newest{ 858 Newest: &ab.SeekNewest{}, 859 }, 860 } 861 862 stop := &ab.SeekPosition{ 863 Type: &ab.SeekPosition_Specified{ 864 Specified: &ab.SeekSpecified{ 865 Number: math.MaxUint64, 866 }, 867 }, 868 } 869 870 seekInfo := &ab.SeekInfo{ 871 Start: start, 872 Stop: stop, 873 Behavior: ab.SeekInfo_BLOCK_UNTIL_READY, 874 } 875 876 env, err := protoutil.CreateSignedEnvelopeWithTLSBinding( 877 pcommon.HeaderType_DELIVER_SEEK_INFO, 878 channelID, 879 signer, 880 seekInfo, 881 int32(0), 882 uint64(0), 883 tlsCertHash, 884 ) 885 if err != nil { 886 logger.Errorf("Error signing envelope: %s", err) 887 return nil 888 } 889 890 return env 891 }