github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/pkg/gateway/endorsement.go (about)

     1  /*
     2  Copyright 2021 IBM All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package gateway
     8  
     9  import (
    10  	"bytes"
    11  	b64 "encoding/base64"
    12  	"sync"
    13  
    14  	"github.com/golang/protobuf/proto"
    15  	"github.com/hyperledger/fabric-protos-go/peer"
    16  )
    17  
    18  type layout struct {
    19  	required     map[string]int // group -> quantity
    20  	endorsements []*peer.Endorsement
    21  }
    22  
    23  // The plan structure is initialised with an endorsement plan from discovery. It is used to manage the
    24  // set of peers to be targeted for endorsement, to collect endorsements received from peers, to provide
    25  // alternative peers for retry in the case of failure, and to trigger when the policy is satisfied.
    26  // Note that this structure and its methods assume that each endorsing peer is in one and only one group.
    27  // This is a constraint of the current algorithm in the discovery service.
    28  type plan struct {
    29  	layouts         []*layout
    30  	groupEndorsers  map[string][]*endorser // group -> endorsing peers
    31  	groupIds        map[string]string      // peer pkiid -> group
    32  	nextLayout      int
    33  	size            int
    34  	responsePayload []byte
    35  	completedLayout *layout
    36  	errorDetails    []proto.Message
    37  	planLock        sync.Mutex
    38  }
    39  
    40  // construct and initialise an endorsement plan
    41  func newPlan(layouts []*layout, groupEndorsers map[string][]*endorser) *plan {
    42  	// Initialize the size with the total number of endorsers across all plans.  This is required to
    43  	// determine the theoretical maximum channel buffer size when requesting endorsement across multiple goroutines.
    44  	var size int
    45  	groupIds := map[string]string{}
    46  	for group, endorsers := range groupEndorsers {
    47  		size += len(endorsers)
    48  		for _, endorser := range endorsers {
    49  			groupIds[endorser.pkiid.String()] = group
    50  		}
    51  	}
    52  	return &plan{
    53  		layouts:        layouts,
    54  		groupEndorsers: groupEndorsers,
    55  		groupIds:       groupIds,
    56  		size:           size,
    57  		planLock:       sync.Mutex{},
    58  	}
    59  }
    60  
    61  // endorsers returns an array of endorsing peers representing the next layout in the list of available layouts
    62  func (p *plan) endorsers() []*endorser {
    63  	p.planLock.Lock()
    64  	defer p.planLock.Unlock()
    65  
    66  	var endorsers []*endorser
    67  	for endorsers == nil {
    68  		// Skip over any defunct layouts.
    69  		for p.nextLayout < len(p.layouts) && p.layouts[p.nextLayout] == nil {
    70  			p.nextLayout++
    71  		}
    72  		if p.nextLayout >= len(p.layouts) {
    73  			return nil
    74  		}
    75  		for group, qty := range p.layouts[p.nextLayout].required {
    76  			if qty > len(p.groupEndorsers[group]) {
    77  				// requires more group endorsers than available - abandon this layout
    78  				endorsers = nil
    79  				p.layouts[p.nextLayout] = nil
    80  				p.nextLayout++
    81  				break
    82  			}
    83  			// remove the first qty endorsers from the front of this group
    84  			endorsers = append(endorsers, p.groupEndorsers[group][0:qty]...)
    85  			p.groupEndorsers[group] = p.groupEndorsers[group][qty:]
    86  		}
    87  	}
    88  	return endorsers
    89  }
    90  
    91  // Invoke processEndorsement when an endorsement has been successfully received for the given endorser.
    92  // All layouts containing the group that contains this endorser are updated with the endorsement.
    93  // Returns Boolean true if endorser returns with a payload that matches the response payloads of
    94  // the other endorsers in the plan.
    95  func (p *plan) processEndorsement(endorser *endorser, response *peer.ProposalResponse) bool {
    96  	p.planLock.Lock()
    97  	defer p.planLock.Unlock()
    98  
    99  	group := p.groupIds[endorser.pkiid.String()]
   100  	endorsers := p.groupEndorsers[group]
   101  
   102  	// remove the endorser from this group
   103  	// this is required if the given endorser was not originally provided by this plan instance (e.g. firstEndorser)
   104  	for i, e := range endorsers {
   105  		if bytes.Equal(e.pkiid, endorser.pkiid) {
   106  			p.groupEndorsers[group] = append(endorsers[:i], endorsers[i+1:]...)
   107  			break
   108  		}
   109  	}
   110  
   111  	// check the proposal responses are the same
   112  	if p.responsePayload == nil {
   113  		p.responsePayload = response.GetPayload()
   114  	} else {
   115  		if !bytes.Equal(p.responsePayload, response.GetPayload()) {
   116  			logger.Warnw("ProposalResponsePayloads do not match (base64)", "payload1", b64.StdEncoding.EncodeToString(p.responsePayload), "payload2", b64.StdEncoding.EncodeToString(response.GetPayload()))
   117  			p.errorDetails = append(p.errorDetails, errorDetail(endorser.endpointConfig, "ProposalResponsePayloads do not match"))
   118  			return false
   119  		}
   120  	}
   121  
   122  	for i := p.nextLayout; i < len(p.layouts); i++ {
   123  		layout := p.layouts[i]
   124  		if layout == nil {
   125  			continue
   126  		}
   127  		if quantity, ok := layout.required[group]; ok {
   128  			layout.required[group] = quantity - 1
   129  			layout.endorsements = append(layout.endorsements, response.Endorsement)
   130  			if layout.required[group] == 0 {
   131  				// this group for this layout is complete - remove from map
   132  				delete(layout.required, group)
   133  				if len(layout.required) == 0 {
   134  					// no groups left - this layout is now satisfied
   135  					p.completedLayout = layout
   136  					return true
   137  				}
   138  			}
   139  		}
   140  	}
   141  	return true
   142  }
   143  
   144  // Invoke nextPeerInGroup if an endorsement fails for the given endorser.
   145  // Returns the next endorser in the same group as given endorser with which to retry the proposal, or nil if there are no more.
   146  func (p *plan) nextPeerInGroup(endorser *endorser) *endorser {
   147  	p.planLock.Lock()
   148  	defer p.planLock.Unlock()
   149  
   150  	group := p.groupIds[endorser.pkiid.String()]
   151  
   152  	// next endorser for this layout
   153  	if len(p.groupEndorsers[group]) > 0 {
   154  		next := p.groupEndorsers[group][0]
   155  		p.groupEndorsers[group] = p.groupEndorsers[group][1:]
   156  		return next
   157  	}
   158  
   159  	// There are no more peers in this group, so will abandon this group entirely and remove all layouts that use it
   160  	// Clearly the current layout was using it, so remove that
   161  	// To avoid memory re-allocations, mark them as nil
   162  	for i := p.nextLayout; i < len(p.layouts); i++ {
   163  		layout := p.layouts[i]
   164  		if layout != nil {
   165  			if _, exists := layout.required[group]; exists {
   166  				p.layouts[i] = nil
   167  			}
   168  		}
   169  	}
   170  	// continue with the next layout
   171  	p.nextLayout++
   172  
   173  	return nil
   174  }
   175  
   176  func (p *plan) addError(detail proto.Message) {
   177  	p.planLock.Lock()
   178  	defer p.planLock.Unlock()
   179  	p.errorDetails = append(p.errorDetails, detail)
   180  }
   181  
   182  func uniqueEndorsements(endorsements []*peer.Endorsement) []*peer.Endorsement {
   183  	endorsersUsed := make(map[string]struct{})
   184  	var unique []*peer.Endorsement
   185  	for _, e := range endorsements {
   186  		if e == nil {
   187  			continue
   188  		}
   189  		key := string(e.Endorser)
   190  		if _, used := endorsersUsed[key]; used {
   191  			continue
   192  		}
   193  		unique = append(unique, e)
   194  		endorsersUsed[key] = struct{}{}
   195  	}
   196  	return unique
   197  }