Skip to content

Commit b49ef1d

Browse files
committed
refactor and fixes after review
1 parent e4b7f8c commit b49ef1d

8 files changed

Lines changed: 109 additions & 52 deletions

File tree

consensus/spos/bls/v2/proofSelfAssembly.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func trySelfAssembleProof(sr *spos.Subround, store signatureEvidenceHandler, ev
3434

3535
bitmap, agg, err := aggregateAndVerifyEvidence(sr, ev)
3636
if err != nil {
37+
stripInvalidShares(sr, ev)
3738
if ev.getCount() < ev.threshold {
3839
// inflated signal was adversarial; the decision tiers demote to the wait tier
3940
store.DropRetained(ev.nonce)
@@ -99,26 +100,24 @@ func abortOnExistingProof(sr *spos.Subround, store signatureEvidenceHandler, ev
99100
return true
100101
}
101102

102-
// aggregateAndVerifyEvidence aggregates the snapshotted shares and verifies the result; on
103-
// failure it strips the invalid shares so a retry or demotion can follow
103+
// aggregateAndVerifyEvidence aggregates the snapshotted shares and verifies the result
104104
func aggregateAndVerifyEvidence(sr *spos.Subround, ev *roundSignatureEvidence) ([]byte, []byte, error) {
105105
bitmap, shares := ev.getAggregationData()
106106

107107
agg, err := sr.SigningHandler().AggregateSigsWithKeys(ev.consensusGroup, bitmap, shares, ev.epoch)
108108
if err == nil {
109109
err = sr.SigningHandler().VerifyAggregatedSigWithKeys(ev.consensusGroup, bitmap, ev.headerHash, agg, ev.epoch)
110110
}
111-
if err == nil {
112-
return bitmap, agg, nil
111+
if err != nil {
112+
return nil, nil, err
113113
}
114114

115-
stripInvalidShares(sr, ev, bitmap, shares)
116-
117-
return nil, nil, err
115+
return bitmap, agg, nil
118116
}
119117

120118
// stripInvalidShares verifies each share in parallel and drops the invalid ones from the evidence
121-
func stripInvalidShares(sr *spos.Subround, ev *roundSignatureEvidence, bitmap []byte, shares [][]byte) {
119+
func stripInvalidShares(sr *spos.Subround, ev *roundSignatureEvidence) {
120+
bitmap, shares := ev.getAggregationData()
122121
invalidIndices := make([]int, 0)
123122
var mut sync.Mutex
124123

consensus/spos/bls/v2/signatureEvidence.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ type signatureEvidenceStore struct {
8686

8787
mut sync.RWMutex
8888
previous *roundSignatureEvidence
89-
retained *roundSignatureEvidence
89+
// retainedQuorum keeps the last quorum-level evidence for a still unsettled nonce beyond
90+
// the one-round lifetime of the previous slot, so self-assembly can be retried
91+
retainedQuorum *roundSignatureEvidence
9092
}
9193

9294
func newSignatureEvidenceStore(proofsPool consensus.EquivalentProofsPool) (*signatureEvidenceStore, error) {
@@ -118,7 +120,7 @@ func (store *signatureEvidenceStore) Capture(ev *roundSignatureEvidence) {
118120
return
119121
}
120122

121-
store.retained = replaced
123+
store.retainedQuorum = replaced
122124
}
123125

124126
// GetPreviousRoundEvidence returns the previous-round slot only if it matches the given
@@ -140,7 +142,7 @@ func (store *signatureEvidenceStore) GetRetainedQuorumEvidence(nonce uint64) (*r
140142
store.mut.RLock()
141143
defer store.mut.RUnlock()
142144

143-
ev := store.retained
145+
ev := store.retainedQuorum
144146
if ev == nil || ev.nonce != nonce {
145147
return nil, false
146148
}
@@ -158,7 +160,7 @@ func (store *signatureEvidenceStore) GetAssemblyCandidate() (*roundSignatureEvid
158160
return ev, true
159161
}
160162

161-
ev = store.retained
163+
ev = store.retainedQuorum
162164
if ev != nil && ev.getCount() >= ev.threshold && !store.isNonceSettled(ev) {
163165
return ev, true
164166
}
@@ -171,8 +173,8 @@ func (store *signatureEvidenceStore) DropRetained(nonce uint64) {
171173
store.mut.Lock()
172174
defer store.mut.Unlock()
173175

174-
if store.retained != nil && store.retained.nonce == nonce {
175-
store.retained = nil
176+
if store.retainedQuorum != nil && store.retainedQuorum.nonce == nonce {
177+
store.retainedQuorum = nil
176178
}
177179
}
178180

consensus/spos/bls/v2/signatureEvidenceFlow_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,34 @@ func TestSubroundStartRound_CaptureSignatureEvidence(t *testing.T) {
158158
assert.False(t, found)
159159
})
160160

161+
t.Run("settled nonce skips the snapshot", func(t *testing.T) {
162+
t.Parallel()
163+
164+
container := consensusMocks.InitConsensusCore()
165+
container.SetSigningHandler(&consensusMocks.SigningHandlerStub{
166+
SignatureShareCalled: func(index uint16) ([]byte, error) {
167+
return []byte(fmt.Sprintf("share_%d", index)), nil
168+
},
169+
})
170+
container.SetEquivalentProofsPool(createSettledProofsPool([]byte("header hash")))
171+
172+
store, _ := newSignatureEvidenceStore(createUnsettledProofsPool())
173+
srStartRound := createFlowStartRound(t, container, store)
174+
175+
srStartRound.SetRoundIndex(10)
176+
srStartRound.SetHeader(&block.Header{Nonce: 5})
177+
srStartRound.SetData([]byte("header hash"))
178+
group := srStartRound.ConsensusGroup()
179+
for i := 0; i < 3; i++ {
180+
_ = srStartRound.SetJobDone(group[i], bls.SrSignature, true)
181+
}
182+
183+
srStartRound.captureSignatureEvidence()
184+
185+
_, found := store.GetPreviousRoundEvidence(5, 11)
186+
assert.False(t, found)
187+
})
188+
161189
t.Run("no signatures observed clears the slot", func(t *testing.T) {
162190
t.Parallel()
163191

consensus/spos/bls/v2/subroundSignature.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,20 +449,21 @@ func (sr *subroundSignature) shouldAbortOnSignatureEvidence(ctx context.Context,
449449
}
450450

451451
count := ev.getCount()
452-
switch {
453-
case count >= ev.threshold:
452+
if count >= ev.threshold {
454453
go trySelfAssembleProof(sr.Subround, sr.signatureEvidence, ev)
455454
log.Debug("refusing to sign competing block: previous round reached quorum",
456455
"nonce", nonce,
457456
"previousHash", ev.headerHash,
458457
"observedShares", count)
459458
return true
460-
case float64(count) >= significantEvidenceFraction*float64(ev.threshold):
459+
}
460+
461+
if float64(count) >= significantEvidenceFraction*float64(ev.threshold) {
461462
return sr.waitForPreviousBlockProof(ctx, ev.headerHash, nonce)
462-
default:
463-
// few shares observed: the previous block lacked support, proceed
464-
return false
465463
}
464+
465+
// few shares observed: the previous block lacked support, proceed
466+
return false
466467
}
467468

468469
// waitIfCompetingBlock waits if this node already signed a different block for the same nonce

consensus/spos/bls/v2/subroundStartRound.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,28 +138,28 @@ func (sr *subroundStartRound) buildSignatureEvidence() *roundSignatureEvidence {
138138
return nil
139139
}
140140

141-
group := sr.ConsensusGroup()
142-
bitmapSize := len(group) / 8
143-
if len(group)%8 != 0 {
144-
bitmapSize++
141+
// settled nonce: the evidence could never be used, skip the snapshot
142+
proof, err := sr.EquivalentProofsPool().GetProofByNonce(header.GetNonce(), header.GetShardID())
143+
if err == nil && !check.IfNil(proof) {
144+
return nil
145145
}
146146

147-
bitmap := make([]byte, bitmapSize)
147+
group := sr.ConsensusGroup()
148+
bitmap := sr.GenerateBitmap(bls.SrSignature)
148149
shares := make([][]byte, len(group))
149150
count := 0
150-
for i, pk := range group {
151-
isJobDone, err := sr.JobDone(pk, bls.SrSignature)
152-
if err != nil || !isJobDone {
151+
for i := range group {
152+
if !isIndexSetInBitmap(i, bitmap) {
153153
continue
154154
}
155155

156156
share, err := sr.SigningHandler().SignatureShare(uint16(i))
157157
if err != nil {
158+
bitmap[i/8] &^= 1 << (uint16(i) % 8)
158159
continue
159160
}
160161

161162
shares[i] = share
162-
bitmap[i/8] |= 1 << (uint16(i) % 8)
163163
count++
164164
}
165165

dataRetriever/dataPool/proofsCache/proofsCache.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ func (pc *proofsCache) addProof(proof data.HeaderProofHandler) {
8080
pc.proofsByHash[newHash] = proof
8181
}
8282

83-
// addProofIfNoneAtNonce adds the proof only if its nonce slot is free or already holds the same
84-
// header hash; returns the added flag and the pre-existing proof for the nonce, if any
83+
// addProofIfNoneAtNonce adds the proof only if its nonce slot is free; an occupied slot (same or
84+
// different hash) rejects the add and returns the pre-existing proof, never overwriting it
8585
func (pc *proofsCache) addProofIfNoneAtNonce(proof data.HeaderProofHandler) (bool, data.HeaderProofHandler) {
8686
if check.IfNil(proof) {
8787
return false, nil

dataRetriever/dataPool/proofsCache/proofsPool.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ func (pp *proofsPool) AddProof(
7373
return pp.addProof(headerProof)
7474
}
7575

76-
// AddProofIfNoneAtNonce will add the provided proof only if its (nonce, shard) slot is free or
77-
// holds the same header hash; unlike AddProof, it never overwrites the pre-existing proof
76+
// AddProofIfNoneAtNonce will add the provided proof only if its (nonce, shard) slot is free; an
77+
// occupied slot (same or different hash) rejects the add and returns the pre-existing proof
7878
func (pp *proofsPool) AddProofIfNoneAtNonce(
7979
headerProof data.HeaderProofHandler,
8080
) (bool, data.HeaderProofHandler) {

factory/crypto/signingHandler.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,12 @@ func (sh *signingHandler) AggregateSigs(bitmap []byte, epoch uint32) ([]byte, er
327327
// AggregateSigsWithKeys aggregates the provided signature shares over the provided group,
328328
// without touching the per-round state; bitmap indices are aligned with pubKeys and sigShares
329329
func (sh *signingHandler) AggregateSigsWithKeys(pubKeys []string, bitmap []byte, sigShares [][]byte, epoch uint32) ([]byte, error) {
330-
signatures, pubKeysSigners, err := sh.selectByBitmap(pubKeys, bitmap, sigShares)
330+
pubKeysSigners, err := sh.selectPubKeysByBitmap(pubKeys, bitmap)
331+
if err != nil {
332+
return nil, err
333+
}
334+
335+
signatures, err := selectSigSharesByBitmap(pubKeys, bitmap, sigShares)
331336
if err != nil {
332337
return nil, err
333338
}
@@ -343,7 +348,7 @@ func (sh *signingHandler) AggregateSigsWithKeys(pubKeys []string, bitmap []byte,
343348
// VerifyAggregatedSigWithKeys verifies the aggregated signature over the provided group,
344349
// without touching the per-round state
345350
func (sh *signingHandler) VerifyAggregatedSigWithKeys(pubKeys []string, bitmap []byte, message []byte, aggSig []byte, epoch uint32) error {
346-
_, pubKeysSigners, err := sh.selectByBitmap(pubKeys, bitmap, nil)
351+
pubKeysSigners, err := sh.selectPubKeysByBitmap(pubKeys, bitmap)
347352
if err != nil {
348353
return err
349354
}
@@ -376,41 +381,63 @@ func (sh *signingHandler) VerifySigShareWithKey(pubKey []byte, sigShare []byte,
376381
return multiSigner.VerifySignatureShareV2(pk, message, sigShare)
377382
}
378383

379-
// selectByBitmap returns the signatures and public keys whose index is set in the bitmap;
380-
// sigShares may be nil when only the public keys are needed
381-
func (sh *signingHandler) selectByBitmap(pubKeys []string, bitmap []byte, sigShares [][]byte) ([][]byte, []crypto.PublicKey, error) {
384+
func validateBitmap(pubKeys []string, bitmap []byte) error {
382385
if bitmap == nil {
383-
return nil, nil, ErrNilBitmap
386+
return ErrNilBitmap
384387
}
385388
if len(bitmap)*8 < len(pubKeys) {
386-
return nil, nil, ErrBitmapMismatch
389+
return ErrBitmapMismatch
387390
}
388-
if sigShares != nil && len(sigShares) != len(pubKeys) {
389-
return nil, nil, ErrIndexOutOfBounds
391+
392+
return nil
393+
}
394+
395+
// selectPubKeysByBitmap returns the public keys whose index is set in the bitmap
396+
func (sh *signingHandler) selectPubKeysByBitmap(pubKeys []string, bitmap []byte) ([]crypto.PublicKey, error) {
397+
err := validateBitmap(pubKeys, bitmap)
398+
if err != nil {
399+
return nil, err
390400
}
391401

392-
signatures := make([][]byte, 0, len(pubKeys))
393402
pubKeysSigners := make([]crypto.PublicKey, 0, len(pubKeys))
394403
for i, pubKeyStr := range pubKeys {
395404
if bitmap[i/8]&(1<<(uint16(i)%8)) == 0 {
396405
continue
397406
}
398407

399-
if sigShares != nil {
400-
if len(sigShares[i]) == 0 {
401-
return nil, nil, ErrNilElement
402-
}
403-
signatures = append(signatures, sigShares[i])
404-
}
405-
406408
pubKey, err := sh.getPubKeyFromBytes([]byte(pubKeyStr))
407409
if err != nil {
408-
return nil, nil, err
410+
return nil, err
409411
}
410412
pubKeysSigners = append(pubKeysSigners, pubKey)
411413
}
412414

413-
return signatures, pubKeysSigners, nil
415+
return pubKeysSigners, nil
416+
}
417+
418+
// selectSigSharesByBitmap returns the signature shares whose index is set in the bitmap
419+
func selectSigSharesByBitmap(pubKeys []string, bitmap []byte, sigShares [][]byte) ([][]byte, error) {
420+
err := validateBitmap(pubKeys, bitmap)
421+
if err != nil {
422+
return nil, err
423+
}
424+
if len(sigShares) != len(pubKeys) {
425+
return nil, ErrIndexOutOfBounds
426+
}
427+
428+
signatures := make([][]byte, 0, len(pubKeys))
429+
for i := range sigShares {
430+
if bitmap[i/8]&(1<<(uint16(i)%8)) == 0 {
431+
continue
432+
}
433+
434+
if len(sigShares[i]) == 0 {
435+
return nil, ErrNilElement
436+
}
437+
signatures = append(signatures, sigShares[i])
438+
}
439+
440+
return signatures, nil
414441
}
415442

416443
func (sh *signingHandler) getPubKeyFromBytes(

0 commit comments

Comments
 (0)