Skip to content

Commit ab18988

Browse files
AkramBitarHayim.Shaul@ibm.com
authored andcommitted
fix(network): narrow distinctness godoc and surface failing MSP in error
selectDistinctForMSPSet now returns ([]view.Identity, string, bool): the string carries the first MSP ID whose pool was exhausted, so the caller can include it in the error message. The error now reads: MSP [Org1MSP] requires 2 distinct endorser(s) but only 1 configured making operator configuration errors immediately diagnosable. The SelectEndorsersForMSPSets godoc now says "distinct by identity bytes" rather than just "distinct", to match what the code actually enforces (byte equality) and distinguish it from the key-equality check in endorserForThresholdRule. The existing test for the unsatisfiable single-endorser case gains three new assertions to pin the MSP name and counts in the error text. All 14 subtests pass; make checks is clean on the affected files. Signed-off-by: Akram Bitar <akram@il.ibm.com> Signed-off-by: AkramBitar <akram@il.ibm.com> Signed-off-by: Hayim.Shaul@ibm.com <hayimsha@fhe03.vpc.cloud9.ibm.com>
1 parent f0bccb8 commit ab18988

2 files changed

Lines changed: 32 additions & 14 deletions

File tree

token/services/network/fabric/endorsement/fsc/selection.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ import (
2222
// A candidate set may list the same MSP ID more than once: a policy such as
2323
// AND(Org1MSP.member, Org1MSP.member) requires that many *distinct* signers from that MSP,
2424
// which is exactly the property protecting against a single misbehaving endorser within the
25-
// organization. Every returned identity is therefore distinct: a slot is never filled with
26-
// an identity already selected for an earlier slot of the same set, and duplicate entries in
27-
// configured collapse to a single candidate endorser. The result always has exactly as many
28-
// elements as the chosen candidate set.
25+
// organization. Every returned identity is therefore distinct by identity bytes: a slot is
26+
// never filled with an identity already selected for an earlier slot of the same set, and
27+
// duplicate entries in configured collapse to a single candidate endorser. The result always
28+
// has exactly as many elements as the chosen candidate set.
2929
//
3030
// A candidate set requiring more distinct signers from an MSP than there are distinct
3131
// configured endorsers in it cannot be satisfied and is skipped, like one naming an MSP with
3232
// no configured endorser at all.
3333
//
3434
// It returns an error if none of the candidate sets can be fully covered by distinct
35-
// configured endorsers.
35+
// configured endorsers. The error names the first MSP that blocked each candidate set.
3636
func SelectEndorsersForMSPSets(configured []view.Identity, mspOf func(view.Identity) (string, error), candidates [][]string) ([]view.Identity, error) {
3737
if len(candidates) == 0 {
3838
return nil, errors.Errorf("no candidate MSP set to satisfy the namespace endorsement policy")
@@ -58,27 +58,41 @@ func SelectEndorsersForMSPSets(configured []view.Identity, mspOf func(view.Ident
5858
byMSP[mspID] = append(byMSP[mspID], id)
5959
}
6060

61+
var setFailures []error
6162
for _, idx := range rand.Perm(len(candidates)) {
62-
if selected, ok := selectDistinctForMSPSet(byMSP, candidates[idx]); ok {
63+
selected, failedMSP, ok := selectDistinctForMSPSet(byMSP, candidates[idx])
64+
if ok {
6365
return selected, nil
6466
}
67+
required := 0
68+
for _, id := range candidates[idx] {
69+
if id == failedMSP {
70+
required++
71+
}
72+
}
73+
available := len(byMSP[failedMSP])
74+
setFailures = append(setFailures, errors.Errorf("MSP [%s] requires %d distinct endorser(s) but only %d configured", failedMSP, required, available))
6575
}
6676

67-
return nil, errors.Join(errors.Errorf("no configured endorser covers any of the [%d] policy-satisfying MSP set(s) with a distinct endorser per required signer", len(candidates)),
77+
return nil, errors.Join(
78+
errors.Errorf("no configured endorser covers any of the [%d] policy-satisfying MSP set(s) with a distinct endorser per required signer", len(candidates)),
79+
errors.Join(setFailures...),
6880
errors.Join(skipped...),
69-
errors.Errorf("failed to resolve MSP"))
81+
errors.Errorf("failed to resolve MSP"),
82+
)
7083
}
7184

7285
// selectDistinctForMSPSet fills one slot per entry of requiredMSPIDs with a random
7386
// configured endorser of that MSP, never reusing an identity already selected for an
74-
// earlier slot of the same set. It returns ok=false if some slot cannot be filled with a
75-
// still-unused endorser, meaning the set is not coverable by distinct endorsers.
87+
// earlier slot of the same set. It returns (selected, "", true) on success, or
88+
// (nil, failedMSP, false) where failedMSP is the first MSP ID whose pool was exhausted,
89+
// meaning the set is not coverable by distinct endorsers.
7690
//
7791
// A greedy per-slot pick is complete here: every slot requiring a given MSP ID draws from
7892
// the same pool, so the only way this fails is that some MSP ID appears in requiredMSPIDs
79-
// more times than that MSP has distinct configured endorsers - genuinely unsatisfiable
93+
// more times than that MSP has distinct configured endorsers genuinely unsatisfiable
8094
// whatever the order of the picks. No backtracking is needed.
81-
func selectDistinctForMSPSet(byMSP map[string][]view.Identity, requiredMSPIDs []string) ([]view.Identity, bool) {
95+
func selectDistinctForMSPSet(byMSP map[string][]view.Identity, requiredMSPIDs []string) ([]view.Identity, string, bool) {
8296
used := make(map[string]struct{}, len(requiredMSPIDs))
8397
selected := make([]view.Identity, 0, len(requiredMSPIDs))
8498
for _, mspID := range requiredMSPIDs {
@@ -90,12 +104,12 @@ func selectDistinctForMSPSet(byMSP map[string][]view.Identity, requiredMSPIDs []
90104
}
91105
}
92106
if len(available) == 0 {
93-
return nil, false
107+
return nil, mspID, false
94108
}
95109
id := available[rand.Intn(len(available))]
96110
used[string(id)] = struct{}{}
97111
selected = append(selected, id)
98112
}
99113

100-
return selected, true
114+
return selected, "", true
101115
}

token/services/network/fabric/endorsement/fsc/selection_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ func TestSelectEndorsersForMSPSets(t *testing.T) {
152152

153153
require.Error(t, err)
154154
assert.Contains(t, err.Error(), "no configured endorser covers")
155+
// Error must name the short MSP and its required-vs-available counts.
156+
assert.Contains(t, err.Error(), "Org1MSP")
157+
assert.Contains(t, err.Error(), "requires 2")
158+
assert.Contains(t, err.Error(), "only 1 configured")
155159
})
156160

157161
t.Run("candidate not coverable by distinct endorsers falls through to the next one", func(t *testing.T) {

0 commit comments

Comments
 (0)