Skip to content

Commit 2190a1c

Browse files
authored
Fixed an error in building the plan for the endorsement. Periodically, the endorse retry - org3 fail & 1 org2 peer fail - requires 2 from org1 test fails with an error. I figured it out and added the TestMultiLayoutFailures1 test, which crashes in the old version of the code. (#5456)
Signed-off-by: Fedor Partanskiy <fredprtnsk@gmail.com>
1 parent de72085 commit 2190a1c

4 files changed

Lines changed: 83 additions & 19 deletions

File tree

internal/pkg/gateway/endorse.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,28 +80,33 @@ func (gs *Server) Endorse(ctx context.Context, request *gp.EndorseRequest) (*gp.
8080
break
8181
}
8282
// send to all the endorsers
83-
waitCh := make(chan bool, len(endorsers))
83+
waitCh := make(chan string, len(endorsers))
8484
for _, e := range endorsers {
8585
go func(e *endorser) {
86+
var g string
8687
for e != nil {
8788
if gs.processProposal(ctx, plan, e, signedProposal, logger) {
8889
break
8990
}
90-
e = plan.nextPeerInGroup(e)
91+
e, g = plan.nextPeerInGroup(e)
9192
}
92-
waitCh <- true
93+
waitCh <- g
9394
}(e)
9495
}
96+
97+
groups := make([]string, 0, len(endorsers))
9598
for range endorsers {
9699
select {
97-
case <-waitCh:
100+
case group := <-waitCh:
98101
// Endorser completedLayout normally
102+
groups = append(groups, group)
99103
case <-ctx.Done():
100104
logger.Warnw("Endorse call timed out while collecting endorsements", "numEndorsers", len(endorsers))
101105
return nil, newRpcError(codes.DeadlineExceeded, "endorsement timeout expired while collecting endorsements")
102106
}
103107
}
104108

109+
plan.abandonGroupRemoveLayouts(groups...)
105110
}
106111

107112
if plan.completedLayout == nil {
@@ -211,8 +216,10 @@ func (gs *Server) planFromFirstEndorser(ctx context.Context, channel string, cha
211216
if remove {
212217
gs.registry.removeEndorser(firstEndorser)
213218
}
214-
firstEndorser = plan.nextPeerInGroup(firstEndorser)
219+
var group string
220+
firstEndorser, group = plan.nextPeerInGroup(firstEndorser)
215221
firstResponse = nil
222+
plan.abandonGroupRemoveLayouts(group)
216223
}
217224
}()
218225
select {

internal/pkg/gateway/endorsement.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func (p *plan) processEndorsement(endorser *endorser, response *peer.ProposalRes
159159

160160
// Invoke nextPeerInGroup if an endorsement fails for the given endorser.
161161
// Returns the next endorser in the same group as given endorser with which to retry the proposal, or nil if there are no more.
162-
func (p *plan) nextPeerInGroup(endorser *endorser) *endorser {
162+
func (p *plan) nextPeerInGroup(endorser *endorser) (*endorser, string) {
163163
p.planLock.Lock()
164164
defer p.planLock.Unlock()
165165

@@ -169,24 +169,34 @@ func (p *plan) nextPeerInGroup(endorser *endorser) *endorser {
169169
if len(p.groupEndorsers[group]) > 0 {
170170
next := p.groupEndorsers[group][0]
171171
p.groupEndorsers[group] = p.groupEndorsers[group][1:]
172-
return next
172+
return next, ""
173173
}
174174

175+
return nil, group
176+
}
177+
178+
func (p *plan) abandonGroupRemoveLayouts(groups ...string) {
175179
// There are no more peers in this group, so will abandon this group entirely and remove all layouts that use it
176180
// Clearly the current layout was using it, so remove that
177181
// To avoid memory re-allocations, mark them as nil
178182
for i := p.nextLayout; i < len(p.layouts); i++ {
179-
layout := p.layouts[i]
180-
if layout != nil {
183+
for _, group := range groups {
184+
if group == "" {
185+
continue
186+
}
187+
188+
layout := p.layouts[i]
189+
if layout == nil {
190+
continue
191+
}
192+
181193
if _, exists := layout.required[group]; exists {
182194
p.layouts[i] = nil
183195
}
184196
}
185197
}
186198
// continue with the next layout
187199
p.nextLayout++
188-
189-
return nil
190200
}
191201

192202
func (p *plan) addError(detail proto.Message) {

internal/pkg/gateway/endorsement_test.go

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ func TestSingleLayoutRetry(t *testing.T) {
6464
response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}}
6565
response3 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e3")}}
6666

67-
retry := plan.nextPeerInGroup(localhostMock)
67+
retry, _ := plan.nextPeerInGroup(localhostMock)
6868
require.Equal(t, peer1Mock, retry)
6969
success := plan.processEndorsement(retry, response1)
7070
require.True(t, success)
7171
require.Nil(t, plan.completedLayout)
7272
success = plan.processEndorsement(peer2Mock, response2)
7373
require.True(t, success)
7474
require.Nil(t, plan.completedLayout)
75-
retry = plan.nextPeerInGroup(peer3Mock)
75+
retry, _ = plan.nextPeerInGroup(peer3Mock)
7676
require.Equal(t, peer4Mock, retry)
7777
success = plan.processEndorsement(retry, response3)
7878
require.True(t, success)
@@ -103,16 +103,17 @@ func TestMultiLayoutRetry(t *testing.T) {
103103
response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}}
104104

105105
// localhost (g1) fails, returns peer1 to retry
106-
retry := plan.nextPeerInGroup(localhostMock)
106+
retry, _ := plan.nextPeerInGroup(localhostMock)
107107
require.Equal(t, peer1Mock, retry)
108108

109109
// peer2 (g2) succeeds
110110
success := plan.processEndorsement(peer2Mock, response1)
111111
require.True(t, success)
112112

113113
// peer1 (g1) also fails - returns nil, since no more peers in g1
114-
retry = plan.nextPeerInGroup(retry)
114+
retry, group := plan.nextPeerInGroup(retry)
115115
require.Nil(t, retry)
116+
plan.abandonGroupRemoveLayouts(group)
116117

117118
// get endorsers for next layout - should be layout 3 because second layout also required g1
118119
endorsers = plan.endorsers()
@@ -153,11 +154,11 @@ func TestMultiLayoutFailures(t *testing.T) {
153154
require.True(t, success)
154155

155156
// peer2 (g2) fails - returns peer3 to retry
156-
retry := plan.nextPeerInGroup(peer2Mock)
157+
retry, _ := plan.nextPeerInGroup(peer2Mock)
157158
require.Equal(t, peer3Mock, retry)
158159

159160
// peer4 (g3) also fails - returns nil, since no more peers in g3
160-
g3retry := plan.nextPeerInGroup(peer4Mock)
161+
g3retry, _ := plan.nextPeerInGroup(peer4Mock)
161162
require.Nil(t, g3retry)
162163

163164
// retry g2 - succeeds
@@ -172,14 +173,57 @@ func TestMultiLayoutFailures(t *testing.T) {
172173
require.Equal(t, peer1Mock, endorsers[0])
173174

174175
// this one fails too
175-
retry = plan.nextPeerInGroup(peer1Mock)
176+
retry, _ = plan.nextPeerInGroup(peer1Mock)
176177
// no more in this group
177178
require.Nil(t, retry)
178179
endorsers = plan.endorsers()
179180
// we've run out of layouts - failed to endorse!
180181
require.Nil(t, endorsers)
181182
}
182183

184+
func TestMultiLayoutFailures1(t *testing.T) {
185+
layouts := []*layout{
186+
{required: map[string]int{"g1": 1, "g2": 2}},
187+
{required: map[string]int{"g1": 2, "g2": 1}},
188+
{required: map[string]int{"g1": 1, "g2": 1, "g3": 1}},
189+
}
190+
groupEndorsers := map[string][]*endorser{
191+
"g1": {localhostMock, peer1Mock},
192+
"g2": {peer2Mock, peer3Mock},
193+
"g3": {peer4Mock},
194+
}
195+
plan := newPlan(layouts, groupEndorsers)
196+
require.Equal(t, plan.size, 5) // total number of endorsers in all layouts
197+
198+
endorsers := plan.endorsers() // first layout
199+
require.Len(t, endorsers, 3)
200+
require.ElementsMatch(t, endorsers, []*endorser{localhostMock, peer2Mock, peer3Mock})
201+
202+
response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}}
203+
response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}}
204+
205+
// localhost (g1) succeeds
206+
success := plan.processEndorsement(localhostMock, response1)
207+
require.True(t, success)
208+
209+
// peer2 (g2) fails - returns nil to retry
210+
retry, group := plan.nextPeerInGroup(peer2Mock)
211+
require.Nil(t, retry)
212+
213+
// peer3 (g2) succeeds
214+
success = plan.processEndorsement(peer3Mock, response2)
215+
require.True(t, success)
216+
217+
plan.abandonGroupRemoveLayouts(group)
218+
219+
// nothing more to try in this layout - get endorsers for next layout
220+
// layout 2 requires a second endorsement from g2, but all g2 peers have been tried - only 1 succeeded
221+
// should return layout 3 which requires a second endorsement from g1
222+
endorsers = plan.endorsers()
223+
require.Len(t, endorsers, 1)
224+
require.Equal(t, peer1Mock, endorsers[0])
225+
}
226+
183227
func TestMultiPlan(t *testing.T) {
184228
layouts1 := []*layout{
185229
{required: map[string]int{"g1": 1}},

internal/pkg/gateway/evaluate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ func (gs *Server) Evaluate(ctx context.Context, request *gp.EvaluateRequest) (*g
7878
gs.registry.removeEndorser(endorser)
7979
}
8080
if retry {
81-
endorser = plan.nextPeerInGroup(endorser)
81+
var group string
82+
endorser, group = plan.nextPeerInGroup(endorser)
83+
plan.abandonGroupRemoveLayouts(group)
84+
8285
} else {
8386
done <- newRpcError(code, "evaluate call to endorser returned error: "+message, errDetails...)
8487
}

0 commit comments

Comments
 (0)