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 }