55 "errors"
66 "sync/atomic"
77 "testing"
8+ "time"
89
910 "github.com/multiversx/mx-chain-core-go/data"
1011 "github.com/multiversx/mx-chain-core-go/data/block"
@@ -99,9 +100,9 @@ func createSelfAssemblySetup(t *testing.T) *selfAssemblyTestSetup {
99100 GetProofByNonceCalled : func (headerNonce uint64 , shardID uint32 ) (data.HeaderProofHandler , error ) {
100101 return nil , errors .New ("proof not found" )
101102 },
102- AddProofCalled : func (headerProof data.HeaderProofHandler ) bool {
103+ AddProofIfNoneAtNonceCalled : func (headerProof data.HeaderProofHandler ) ( bool , data. HeaderProofHandler ) {
103104 numAdded .Add (1 )
104- return true
105+ return true , nil
105106 },
106107 }
107108 container .SetEquivalentProofsPool (proofsPool )
@@ -211,7 +212,7 @@ func TestTrySelfAssembleProof_HappyPath(t *testing.T) {
211212 assert .Nil (t , err , "the self-assembled aggregated signature must verify" )
212213}
213214
214- func TestTrySelfAssembleProof_AssemblyStartedOnlyOnce (t * testing.T ) {
215+ func TestTrySelfAssembleProof_SingleAttemptPerRound (t * testing.T ) {
215216 t .Parallel ()
216217
217218 setup := createSelfAssemblySetup (t )
@@ -220,7 +221,7 @@ func TestTrySelfAssembleProof_AssemblyStartedOnlyOnce(t *testing.T) {
220221 trySelfAssembleProof (setup .subround , setup .store , ev )
221222 trySelfAssembleProof (setup .subround , setup .store , ev )
222223
223- assert .Equal (t , int32 (1 ), setup .numAdded .Load (), "the CAS guard must prevent a second assembly " )
224+ assert .Equal (t , int32 (1 ), setup .numAdded .Load (), "a second attempt in the same round must be skipped " )
224225}
225226
226227func TestTrySelfAssembleProof_StripsInvalidSharesAndRetries (t * testing.T ) {
@@ -297,18 +298,20 @@ func TestTrySelfAssembleProof_AbortsOnCompetingProof(t *testing.T) {
297298 setup := createSelfAssemblySetup (t )
298299 ev := setup .createRealEvidence (t , []byte ("header hash" ), 7 )
299300
300- var numChecks atomic.Int32
301- setup .proofsPool .GetProofByNonceCalled = func (headerNonce uint64 , shardID uint32 ) (data.HeaderProofHandler , error ) {
302- if numChecks .Add (1 ) == 1 {
303- return nil , errors .New ("proof not found" )
304- }
305- return & block.HeaderProof {HeaderHash : []byte ("competing hash" ), HeaderNonce : headerNonce }, nil
301+ // entry check finds no proof, the atomic add is beaten by a competing proof
302+ setup .proofsPool .AddProofIfNoneAtNonceCalled = func (headerProof data.HeaderProofHandler ) (bool , data.HeaderProofHandler ) {
303+ return false , & block.HeaderProof {HeaderHash : []byte ("competing hash" ), HeaderNonce : headerProof .GetHeaderNonce ()}
306304 }
307305
306+ setup .store .Capture (ev )
307+ setup .store .Capture (nil )
308+
308309 trySelfAssembleProof (setup .subround , setup .store , ev )
309310
310- assert .Nil (t , setup .broadcast .Load (), "broadcast must be aborted by the pre-publish re-check" )
311- assert .Equal (t , int32 (0 ), setup .numAdded .Load ())
311+ assert .Nil (t , setup .broadcast .Load (), "broadcast must be aborted by the atomic add" )
312+
313+ _ , ok := setup .store .GetRetainedQuorumEvidence (ev .nonce )
314+ assert .False (t , ok , "settled nonce must drop the retained slot" )
312315 })
313316
314317 t .Run ("proof for the own hash means already done" , func (t * testing.T ) {
@@ -348,6 +351,131 @@ func TestTrySelfAssembleProof_EpochBoundaryUsesSnapshotGroup(t *testing.T) {
348351 assert .Nil (t , err )
349352}
350353
354+ func TestTrySelfAssembleProof_InProgressAttemptBlocksUntilDone (t * testing.T ) {
355+ t .Parallel ()
356+
357+ var currentRound atomic.Int64
358+ currentRound .Store (10 )
359+
360+ proceed := make (chan struct {})
361+ started := make (chan struct {}, 4 )
362+ var numAggregations atomic.Int32
363+
364+ container := consensusMocks .InitConsensusCore ()
365+ container .SetRoundHandler (& testscommon.RoundHandlerMock {
366+ IndexCalled : func () int64 {
367+ return currentRound .Load ()
368+ },
369+ })
370+ container .SetSigningHandler (& consensusMocks.SigningHandlerStub {
371+ AggregateSigsWithKeysCalled : func (pubKeys []string , bitmap []byte , sigShares [][]byte , epoch uint32 ) ([]byte , error ) {
372+ numAggregations .Add (1 )
373+ started <- struct {}{}
374+ <- proceed
375+ return []byte ("agg" ), nil
376+ },
377+ })
378+ container .SetEquivalentProofsPool (createUnsettledProofsPool ())
379+
380+ sr := createFlowSubround (t , container , bls .SrSignature , "(SIGNATURE)" )
381+ store , _ := newSignatureEvidenceStore (createUnsettledProofsPool ())
382+ ev := createTestEvidence (9 , 5 , 7 , 8 )
383+
384+ // first attempt starts in round 10 and blocks inside the aggregation
385+ go trySelfAssembleProof (sr , store , ev )
386+ <- started
387+
388+ // next round while the first attempt is still executing: no second attempt
389+ currentRound .Store (11 )
390+ trySelfAssembleProof (sr , store , ev )
391+ assert .Equal (t , int32 (1 ), numAggregations .Load ())
392+
393+ close (proceed )
394+ require .Eventually (t , func () bool {
395+ return ! ev .assemblyRunning .Load ()
396+ }, time .Second , time .Millisecond )
397+
398+ // round 11 had no attempt started, so a retry is allowed after completion
399+ trySelfAssembleProof (sr , store , ev )
400+ assert .Equal (t , int32 (2 ), numAggregations .Load ())
401+
402+ // a second attempt in the same round is skipped
403+ trySelfAssembleProof (sr , store , ev )
404+ assert .Equal (t , int32 (2 ), numAggregations .Load ())
405+
406+ // next round: retried again
407+ currentRound .Store (12 )
408+ trySelfAssembleProof (sr , store , ev )
409+ assert .Equal (t , int32 (3 ), numAggregations .Load ())
410+ }
411+
412+ func TestTrySelfAssembleProof_SettledNonceDropsRetainedEvenWhenIneligible (t * testing.T ) {
413+ t .Parallel ()
414+
415+ var numAggregations atomic.Int32
416+ container := consensusMocks .InitConsensusCore ()
417+ container .SetSigningHandler (& consensusMocks.SigningHandlerStub {
418+ AggregateSigsWithKeysCalled : func (pubKeys []string , bitmap []byte , sigShares [][]byte , epoch uint32 ) ([]byte , error ) {
419+ numAggregations .Add (1 )
420+ return []byte ("agg" ), nil
421+ },
422+ })
423+ container .SetEquivalentProofsPool (createSettledProofsPool ([]byte ("competing hash" )))
424+
425+ sr := createFlowSubround (t , container , bls .SrSignature , "(SIGNATURE)" )
426+ store , _ := newSignatureEvidenceStore (createUnsettledProofsPool ())
427+
428+ ev := createTestEvidence (9 , 5 , 7 , 8 )
429+ for i := range ev .consensusGroup {
430+ ev .consensusGroup [i ] = "other_" + ev .consensusGroup [i ]
431+ }
432+ store .Capture (ev )
433+ store .Capture (nil )
434+ _ , ok := store .GetRetainedQuorumEvidence (ev .nonce )
435+ require .True (t , ok )
436+
437+ trySelfAssembleProof (sr , store , ev )
438+
439+ assert .Equal (t , int32 (0 ), numAggregations .Load ())
440+ _ , ok = store .GetRetainedQuorumEvidence (ev .nonce )
441+ assert .False (t , ok , "the settled nonce must drop the retained slot before the eligibility check" )
442+ }
443+
444+ func TestTrySelfAssembleProof_KeepsEvidenceWhenAllSharesFailVerification (t * testing.T ) {
445+ t .Parallel ()
446+
447+ var numAdded atomic.Int32
448+ container := consensusMocks .InitConsensusCore ()
449+ container .SetSigningHandler (& consensusMocks.SigningHandlerStub {
450+ AggregateSigsWithKeysCalled : func (pubKeys []string , bitmap []byte , sigShares [][]byte , epoch uint32 ) ([]byte , error ) {
451+ return nil , errors .New ("infrastructure error" )
452+ },
453+ VerifySigShareWithKeyCalled : func (pubKey []byte , sigShare []byte , message []byte , epoch uint32 ) error {
454+ return errors .New ("infrastructure error" )
455+ },
456+ })
457+ proofsPool := createUnsettledProofsPool ()
458+ proofsPool .AddProofIfNoneAtNonceCalled = func (headerProof data.HeaderProofHandler ) (bool , data.HeaderProofHandler ) {
459+ numAdded .Add (1 )
460+ return true , nil
461+ }
462+ container .SetEquivalentProofsPool (proofsPool )
463+
464+ sr := createFlowSubround (t , container , bls .SrSignature , "(SIGNATURE)" )
465+ store , _ := newSignatureEvidenceStore (createUnsettledProofsPool ())
466+
467+ ev := createTestEvidence (9 , 5 , 7 , 8 )
468+ store .Capture (ev )
469+ store .Capture (nil )
470+
471+ trySelfAssembleProof (sr , store , ev )
472+
473+ assert .Equal (t , 8 , ev .getCount (), "evidence must stay intact on an all-shares failure" )
474+ assert .Equal (t , int32 (0 ), numAdded .Load ())
475+ _ , ok := store .GetRetainedQuorumEvidence (ev .nonce )
476+ assert .True (t , ok , "no demotion on an all-shares failure" )
477+ }
478+
351479func TestTrySelfAssembleProof_NotEligibleWithoutGroupKeys (t * testing.T ) {
352480 t .Parallel ()
353481
0 commit comments