Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FABGW-25 Endorse using generated ChaincodeInterest #2773

Merged
merged 1 commit into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 88 additions & 10 deletions internal/pkg/gateway/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*g
return nil, status.Error(codes.InvalidArgument, "an evaluate request is required")
}
signedProposal := request.GetProposedTransaction()
channel, chaincodeID, err := getChannelAndChaincodeFromSignedProposal(signedProposal)
channel, chaincodeID, _, err := getChannelAndChaincodeFromSignedProposal(signedProposal)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to unpack transaction proposal: %s", err)
}

err = gs.registry.registerChannel(channel)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
}

endorser, err := gs.registry.evaluator(channel, chaincodeID, request.GetTargetOrganizations())
if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
Expand Down Expand Up @@ -68,7 +73,7 @@ func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*g
Result: retVal,
}

logger.Debugw("Evaluate call to endorser returned success", "channel", request.ChannelId, "txid", request.TransactionId, "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "status", retVal.Status, "message", retVal.Message)
logger.Debugw("Evaluate call to endorser returned success", "channel", request.ChannelId, "txid", request.TransactionId, "endorserAddress", endorser.endpointConfig.address, "endorserMspid", endorser.endpointConfig.mspid, "status", retVal.GetStatus(), "message", retVal.GetMessage())
return evaluateResponse, nil
}

Expand All @@ -86,31 +91,105 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp.
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to unpack transaction proposal: %s", err)
}
channel, chaincodeID, err := getChannelAndChaincodeFromSignedProposal(signedProposal)
channel, chaincodeID, hasTransientData, err := getChannelAndChaincodeFromSignedProposal(signedProposal)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to unpack transaction proposal: %s", err)
}

var endorsers []*endorser
if len(request.EndorsingOrganizations) > 0 {
endorsers, err = gs.registry.endorsersForOrgs(channel, chaincodeID, request.EndorsingOrganizations)
} else {
endorsers, err = gs.registry.endorsers(channel, chaincodeID)
}
err = gs.registry.registerChannel(channel)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
}

ctx, cancel := context.WithTimeout(ctx, gs.options.EndorsementTimeout)
defer cancel()

defaultInterest := &peer.ChaincodeInterest{
Chaincodes: []*peer.ChaincodeCall{{
Name: chaincodeID,
}},
}

var endorsers []*endorser
var responses []*peer.ProposalResponse
if len(request.EndorsingOrganizations) > 0 {
// The client is specifying the endorsing orgs and taking responsibility for ensuring it meets the signature policy
endorsers, err = gs.registry.endorsersForOrgs(channel, chaincodeID, request.EndorsingOrganizations)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
}
} else {
// The client is delegating choice of endorsers to the gateway.

// 1. Choose an endorser from the gateway's organization
var firstEndorser *endorser
es, ok := gs.registry.endorsersByOrg(channel, chaincodeID)[gs.registry.localEndorser.mspid]
if !ok {
// No local org endorsers for this channel/chaincode. If transient data is involved, return error
if hasTransientData {
return nil, status.Error(codes.FailedPrecondition, "no endorsers found in the gateway's organization; retry specifying endorsing organization(s) to protect transient data")
}
// Otherwise, just let discovery pick one.
endorsers, err = gs.registry.endorsers(channel, defaultInterest, "")
Comment on lines +132 to +133
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking a loud here - Is it worth a blind try or simply let client choose the gateway of the org that it thinks should succeed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to consider a future proposal for allowing the client to choose the 'initial' endorser

if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
}
firstEndorser = endorsers[0]
} else {
firstEndorser = es[0].endorser
}

gs.logger.Debugw("Sending to first endorser:", "channel", channel, "chaincode", chaincodeID, "MSPID", firstEndorser.mspid, "endpoint", firstEndorser.address)

// 2. Process the proposal on this endorser
firstResponse, err := firstEndorser.client.ProcessProposal(ctx, signedProposal)
if err != nil {
return nil, rpcError(codes.Aborted, "failed to endorse transaction", endpointError(firstEndorser, err))
}
if firstResponse.Response.Status < 200 || firstResponse.Response.Status >= 400 {
return nil, rpcError(codes.Aborted, "failed to endorse transaction", endpointError(firstEndorser, fmt.Errorf("error %d, %s", firstResponse.Response.Status, firstResponse.Response.Message)))
}

// 3. Extract ChaincodeInterest and SBE policies
// The chaincode interest could be nil for legacy peers and for chaincode functions that don't produce a read-write set
interest := firstResponse.Interest
if len(interest.GetChaincodes()) == 0 {
interest = defaultInterest
}
Comment on lines +156 to +158
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A clarification question - Will this condition be ever true unless it's a read-only transaction? If no, then, is there any purpose of proceeding from here?

Also, that brings another thought, what if it's read-only transaction for pvtdata? That would record some entries in the interest which would give a wrong signal for proceeding.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be true if:

  • The Interest structure in the ProposalResponse is nil, which will be the case for legacy peers
  • The chaincode invocation doesn't create a RWset. This happens in one of the integration tests where the chaincode only sets an event


// 4. If transient data is involved, then we need to ensure that discovery only returns orgs which own the collections involved.
// Do this by setting NoPrivateReads to false on each collection
if hasTransientData {
for _, call := range interest.GetChaincodes() {
call.NoPrivateReads = false
}
}

// 5. Get a set of endorsers from discovery via the registry
// The preferred discovery layout will contain the firstEndorser's Org.
endorsers, err = gs.registry.endorsers(channel, interest, firstEndorser.mspid)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "%s", err)
}

// 6. Remove the gateway org's endorser, since we've already done that
for i, e := range endorsers {
if e.mspid == firstEndorser.mspid {
endorsers = append(endorsers[:i], endorsers[i+1:]...)
responses = append(responses, firstResponse)
break
}
}
}

var wg sync.WaitGroup
responseCh := make(chan *endorserResponse, len(endorsers))
// send to all the endorsers
for _, e := range endorsers {
wg.Add(1)
go func(e *endorser) {
defer wg.Done()
gs.logger.Debugw("Sending to endorser:", "channel", channel, "chaincode", chaincodeID, "MSPID", e.mspid, "endpoint", e.address)
response, err := e.client.ProcessProposal(ctx, signedProposal)
switch {
case err != nil:
Expand All @@ -129,7 +208,6 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp.
wg.Wait()
close(responseCh)

var responses []*peer.ProposalResponse
var errorDetails []proto.Message
for response := range responseCh {
if response.err != nil {
Expand Down
Loading