Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
573aef0
optimistic sig share creation for managed keys
ssd04 Apr 15, 2026
faebd8f
fixes after review
ssd04 Apr 15, 2026
4800fee
trigger sigs creation from block subround
ssd04 Apr 24, 2026
170d42a
fix race in tests
ssd04 Apr 27, 2026
7b76320
update context timeout
ssd04 Apr 27, 2026
4bbc8ea
trigger sigs creation from block subround - without timeout ctx
ssd04 Apr 28, 2026
ab81017
add context in singing handler for create sig
ssd04 Apr 28, 2026
f88f1e5
fix test
ssd04 Apr 28, 2026
fb7b9b7
extra logging
ssd04 May 4, 2026
40875b5
adjust time left
ssd04 May 5, 2026
197b42d
add context for managed signatures broadcast
ssd04 May 5, 2026
d20fca5
update tests
ssd04 May 5, 2026
7faae14
log for aggregated sig
ssd04 May 5, 2026
099ce93
update logging for triggered optimistic sigs
ssd04 May 5, 2026
1ee6f23
Merge branch 'feat/testnet-fixes' into optimistic-sig-share-creation
AdoAdoAdo May 19, 2026
735f94d
Merge branch 'feat/testnet-fixes' into optimistic-sig-share-creation
ssd04 May 27, 2026
cd91661
fixes after review - signing handler
ssd04 May 27, 2026
5de6697
fixes after review - sigs trigger
ssd04 May 27, 2026
d195344
fixes after review
ssd04 May 27, 2026
3b23668
Merge branch 'feat/testnet-fixes' into optimistic-sig-share-creation
ssd04 Jun 15, 2026
832a01c
fixes after review
ssd04 Jun 15, 2026
e4d6eb1
Merge branch 'optimistic-sig-share-creation' into managed-sigs-context
ssd04 Jun 15, 2026
da0c4b2
Merge pull request #7839 from multiversx/managed-sigs-context
ssd04 Jun 16, 2026
56ee618
Merge branch 'feat/testnet-fixes' into optimistic-sig-share-creation
ssd04 Jun 16, 2026
f20cfd2
fixes after review
ssd04 Jun 16, 2026
7628f43
refactor sigs trigger
ssd04 Jun 16, 2026
0f74004
fix unit test
ssd04 Jun 16, 2026
490ad26
fix context in test
ssd04 Jun 16, 2026
2cea8d5
cleanup logs
ssd04 Jun 17, 2026
6563f97
fix goroutines throttler ctx
ssd04 Jun 17, 2026
4467501
drain not triggered goroutines
ssd04 Jun 18, 2026
829d267
Merge branch 'feat/testnet-fixes' into optimistic-sig-share-creation
AdoAdoAdo Jun 18, 2026
58fa573
Merge branch 'feat/testnet-fixes' into optimistic-sig-share-creation
ssd04 Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion consensus/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ type PeerBlacklistHandler interface {
// SigningHandler defines the behaviour of a component that handles multi and single signatures used in consensus operations
type SigningHandler interface {
Reset(pubKeys []string) error
CreateSignatureShareForPublicKey(message []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error)
CreateSignatureShareForPublicKey(ctx context.Context, message []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error)
CreateSignatureForPublicKey(message []byte, publicKeyBytes []byte) ([]byte, error)
VerifySingleSignature(publicKeyBytes []byte, message []byte, signature []byte) error
StoreSignatureShare(index uint16, sig []byte) error
Expand Down
8 changes: 5 additions & 3 deletions consensus/spos/bls/v1/subroundSignature.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func checkNewSubroundSignatureParams(
}

// doSignatureJob method does the job of the subround Signature
func (sr *subroundSignature) doSignatureJob(_ context.Context) bool {
func (sr *subroundSignature) doSignatureJob(ctx context.Context) bool {
if !sr.CanDoSubroundJob(sr.Current()) {
return false
}
Expand All @@ -93,6 +93,7 @@ func (sr *subroundSignature) doSignatureJob(_ context.Context) bool {
}

signatureShare, err := sr.SigningHandler().CreateSignatureShareForPublicKey(
ctx,
sr.GetData(),
uint16(selfIndex),
sr.GetHeader().GetEpoch(),
Expand All @@ -116,7 +117,7 @@ func (sr *subroundSignature) doSignatureJob(_ context.Context) bool {
}
}

return sr.doSignatureJobForManagedKeys()
return sr.doSignatureJobForManagedKeys(ctx)
}

func (sr *subroundSignature) createAndSendSignatureMessage(signatureShare []byte, pkBytes []byte) bool {
Expand Down Expand Up @@ -351,7 +352,7 @@ func (sr *subroundSignature) remainingTime() time.Duration {
return remainigTime
}

func (sr *subroundSignature) doSignatureJobForManagedKeys() bool {
func (sr *subroundSignature) doSignatureJobForManagedKeys(ctx context.Context) bool {
isMultiKeyLeader := sr.IsMultiKeyLeaderInCurrentRound()

numMultiKeysSignaturesSent := 0
Expand All @@ -371,6 +372,7 @@ func (sr *subroundSignature) doSignatureJobForManagedKeys() bool {
}

signatureShare, err := sr.SigningHandler().CreateSignatureShareForPublicKey(
ctx,
sr.GetData(),
uint16(selfIndex),
sr.GetHeader().GetEpoch(),
Expand Down
9 changes: 5 additions & 4 deletions consensus/spos/bls/v1/subroundSignature_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1_test

import (
"context"
"testing"

"github.com/multiversx/mx-chain-core-go/core/check"
Expand Down Expand Up @@ -360,7 +361,7 @@ func TestSubroundSignature_DoSignatureJob(t *testing.T) {

err := errors.New("create signature share error")
signingHandler := &consensusMocks.SigningHandlerStub{
CreateSignatureShareForPublicKeyCalled: func(msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
CreateSignatureShareForPublicKeyCalled: func(_ context.Context, msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
return nil, err
},
}
Expand All @@ -370,7 +371,7 @@ func TestSubroundSignature_DoSignatureJob(t *testing.T) {
assert.False(t, r)

signingHandler = &consensusMocks.SigningHandlerStub{
CreateSignatureShareForPublicKeyCalled: func(msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
CreateSignatureShareForPublicKeyCalled: func(_ context.Context, msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
return []byte("SIG"), nil
},
}
Expand Down Expand Up @@ -441,7 +442,7 @@ func TestSubroundSignature_DoSignatureJobWithMultikey(t *testing.T) {

err := errors.New("create signature share error")
signingHandler := &consensusMocks.SigningHandlerStub{
CreateSignatureShareForPublicKeyCalled: func(msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
CreateSignatureShareForPublicKeyCalled: func(_ context.Context, msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
return nil, err
},
}
Expand All @@ -451,7 +452,7 @@ func TestSubroundSignature_DoSignatureJobWithMultikey(t *testing.T) {
assert.False(t, r)

signingHandler = &consensusMocks.SigningHandlerStub{
CreateSignatureShareForPublicKeyCalled: func(msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
CreateSignatureShareForPublicKeyCalled: func(_ context.Context, msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) {
return []byte("SIG"), nil
},
}
Expand Down
5 changes: 3 additions & 2 deletions consensus/spos/bls/v2/benchmark_send_proof_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v2_test

import (
"context"
"sort"
"testing"

Expand Down Expand Up @@ -171,7 +172,7 @@ func benchmarkSendProof(b *testing.B, numberOfKeys int) {

// Create signature shares for all validators (simulating that all have signed)
for i := 0; i < len(keys); i++ {
_, err := signingHandler.CreateSignatureShareForPublicKey(dataToBeSigned, uint16(i), 0, []byte(keys[i]))
_, err := signingHandler.CreateSignatureShareForPublicKey(context.TODO(), dataToBeSigned, uint16(i), 0, []byte(keys[i]))
require.Nil(b, err)
err = srEndRound.SetJobDone(keys[i], bls.SrSignature, true)
require.Nil(b, err)
Expand All @@ -183,7 +184,7 @@ func benchmarkSendProof(b *testing.B, numberOfKeys int) {
for i := 0; i < b.N; i++ {
// Reset signature state for each iteration
for j := 0; j < len(keys); j++ {
_, _ = signingHandler.CreateSignatureShareForPublicKey(dataToBeSigned, uint16(j), 0, []byte(keys[j]))
_, _ = signingHandler.CreateSignatureShareForPublicKey(context.TODO(), dataToBeSigned, uint16(j), 0, []byte(keys[j]))
}

b.StartTimer()
Expand Down
2 changes: 1 addition & 1 deletion consensus/spos/bls/v2/benchmark_verify_signatures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func BenchmarkSubroundEndRound_VerifyNodesOnAggSigFailTime(b *testing.B) {

sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState)
for i := 0; i < len(sr.ConsensusGroup()); i++ {
_, err := sr.SigningHandler().CreateSignatureShareForPublicKey(dataToBeSigned, uint16(i), sr.EnableEpochsHandler().GetCurrentEpoch(), []byte(keys[i]))
_, err := sr.SigningHandler().CreateSignatureShareForPublicKey(context.TODO(), dataToBeSigned, uint16(i), sr.EnableEpochsHandler().GetCurrentEpoch(), []byte(keys[i]))
require.Nil(b, err)
_ = sr.SetJobDone(keys[i], bls.SrSignature, true)
}
Expand Down
1 change: 1 addition & 0 deletions consensus/spos/bls/v2/blsSubroundsFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ func (fct *factory) generateBlockSubround() error {
processingThresholdPercent,
fct.worker,
syncController,
fct.signatureThrottler,
)
if err != nil {
return err
Expand Down
30 changes: 30 additions & 0 deletions consensus/spos/bls/v2/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package v2

import (
"context"
"fmt"
"time"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-go/consensus/spos"
)

func checkGoRoutinesThrottler(
ctx context.Context,
signatureThrottler core.Throttler,
) error {
for {
if signatureThrottler.CanProcess() {
break
}

select {
case <-time.After(timeSpentBetweenChecks):
continue
case <-ctx.Done():
return fmt.Errorf("%w while checking the throttler", spos.ErrTimeIsOut)
}
}

return nil
}
7 changes: 6 additions & 1 deletion consensus/spos/bls/v2/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func (sr *subroundBlock) SendBlockBody(body data.BodyHandler, marshalizedBody []

// SendBlockHeader method sends the proposed block header in the subround Block
func (sr *subroundBlock) SendBlockHeader(header data.HeaderHandler, marshalizedHeader []byte) bool {
return sr.sendBlockHeader(header, marshalizedHeader)
return sr.sendBlockHeader(context.TODO(), header, marshalizedHeader)
}

// ComputeSubroundProcessingMetric computes processing metric related to the subround Block
Expand Down Expand Up @@ -395,3 +395,8 @@ func (sr *subroundEndRound) UpdateNonceDeltaMetrics() {
func (sr *subroundBlock) PrepareBlockForExecution(header data.HeaderHandler, body data.BodyHandler) error {
return sr.prepareBlockForExecution(header, body)
}

// TriggerCreateSignaturesForManagedKeys -
func (sr *subroundBlock) TriggerCreateSignaturesForManagedKeys(ctx context.Context, headerHash []byte, headerHandler data.HeaderHandler) {
sr.triggerCreateSignaturesForManagedKeys(ctx, headerHash, headerHandler)
}
121 changes: 118 additions & 3 deletions consensus/spos/bls/v2/subroundBlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
"github.com/multiversx/mx-chain-go/process/asyncExecution/cache"
)

type consensusPubKey struct {
idx int
pkBytes []byte
}

// maxAllowedSizeInBytes defines how many bytes are allowed as payload in a message
const maxAllowedSizeInBytes = uint32(core.MegabyteSize * 95 / 100)

Expand All @@ -29,6 +34,7 @@ type subroundBlock struct {
worker spos.WorkerHandler
mutBlockProcessing sync.Mutex
syncController spos.NtpSyncControllerHandler
signatureThrottler core.Throttler
}

// NewSubroundBlock creates a subroundBlock object
Expand All @@ -37,6 +43,7 @@ func NewSubroundBlock(
processingThresholdPercentage int,
worker spos.WorkerHandler,
syncController spos.NtpSyncControllerHandler,
signatureThrottler core.Throttler,
) (*subroundBlock, error) {
err := checkNewSubroundBlockParams(baseSubround)
if err != nil {
Expand All @@ -49,12 +56,16 @@ func NewSubroundBlock(
if check.IfNil(syncController) {
return nil, ErrNilRoundSyncController
}
if check.IfNil(signatureThrottler) {
return nil, spos.ErrNilThrottler
}

srBlock := subroundBlock{
Subround: baseSubround,
processingThresholdPercentage: processingThresholdPercentage,
worker: worker,
syncController: syncController,
signatureThrottler: signatureThrottler,
}

srBlock.Job = srBlock.doBlockJob
Expand Down Expand Up @@ -142,7 +153,7 @@ func (sr *subroundBlock) doBlockJob(ctx context.Context) bool {
return false
}

sentWithSuccess := sr.sendBlock(header, body, leader)
sentWithSuccess := sr.sendBlock(ctx, header, body, leader)
if !sentWithSuccess {
return false
}
Expand Down Expand Up @@ -220,7 +231,12 @@ func printLogMessage(ctx context.Context, baseMessage string, err error) {
log.Debug(baseMessage, "error", err.Error())
}

func (sr *subroundBlock) sendBlock(header data.HeaderHandler, body data.BodyHandler, leader string) bool {
func (sr *subroundBlock) sendBlock(
ctx context.Context,
header data.HeaderHandler,
body data.BodyHandler,
leader string,
) bool {
marshalledBody, err := sr.Marshalizer().Marshal(body)
if err != nil {
log.Debug("sendBlock.Marshal: body", "error", err.Error())
Expand All @@ -236,7 +252,7 @@ func (sr *subroundBlock) sendBlock(header data.HeaderHandler, body data.BodyHand
sr.logBlockSize(marshalledBody, marshalledHeader)
headerHash := sr.Hasher().Compute(string(marshalledHeader))

if !sr.sendBlockBody(body, marshalledBody) || !sr.sendBlockHeader(header, headerHash) {
if !sr.sendBlockBody(body, marshalledBody) || !sr.sendBlockHeader(ctx, header, headerHash) {
return false
}

Expand Down Expand Up @@ -313,6 +329,7 @@ func (sr *subroundBlock) sendBlockBody(

// sendBlockHeader method sends the proposed block header in the subround Block
func (sr *subroundBlock) sendBlockHeader(
ctx context.Context,
headerHandler data.HeaderHandler,
headerHash []byte,
) bool {
Expand All @@ -336,6 +353,8 @@ func (sr *subroundBlock) sendBlockHeader(
sr.SetData(headerHash)
sr.SetHeader(headerHandler)

sr.triggerCreateSignaturesForManagedKeys(ctx, headerHash, headerHandler)

// log the header output for debugging purposes
headerOutput, err := common.PrettifyStruct(headerHandler)
if err == nil {
Expand All @@ -345,6 +364,100 @@ func (sr *subroundBlock) sendBlockHeader(
return true
}

func (sr *subroundBlock) triggerCreateSignaturesForManagedKeys(
ctx context.Context,
headerHash []byte,
headerHandler data.HeaderHandler,
) {
if check.IfNil(headerHandler) {
log.Debug("triggerCreateSignaturesForManagedKeys: triggered with nil header")
return
}

if sr.RoundHandler().Index() != int64(headerHandler.GetRound()) {
log.Debug("triggerCreateSignaturesForManagedKeys: not for current round",
"currentRound", sr.RoundHandler().Index(),
"headerRound", headerHandler.GetRound(),
)
return
}

currentEpoch := headerHandler.GetEpoch()

sigSubroundEndTime := time.Duration(float64(sr.RoundHandler().TimeDuration()) * srSignatureEndTime)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also if triggered on a goroutine, maybe check that the round on the header is still the round in the RoundHandler, otherwise exit without triggering signing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

timeLeft := sr.RoundHandler().RemainingTime(sr.RoundHandler().TimeStamp(), sigSubroundEndTime)
sigCtx, cancel := context.WithTimeout(ctx, timeLeft)
sr.SetSignaturesCtxCancelFunc(cancel)

keys := sr.getManagedKeysByIndex()
if len(keys) == 0 {
return
}

wg := sr.SignaturesWaitGroup()
wg.Add(len(keys))

go func() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that was the intention, to trigger asynchronously, due to goroutines throttler which might block on limit reached

triggered := 0
for _, pk := range keys {
err := checkGoRoutinesThrottler(sigCtx, sr.signatureThrottler)
if err != nil {
log.Debug("triggerCreateSignaturesForManagedKeys.checkGoRoutinesThrottler", "err", err)
cancel()
Comment thread
AdoAdoAdo marked this conversation as resolved.

for i := triggered; i < len(keys); i++ {
wg.Done()
}

return
}
sr.signatureThrottler.StartProcessing()

triggered++

go func(sigCtx context.Context, idx int, pkBytes []byte) {
defer sr.signatureThrottler.EndProcessing()
defer wg.Done()

select {
case <-sigCtx.Done():
log.Info("triggerCreateSignaturesForManagedKeys: context done", "timeLeft", timeLeft)
return
default:
}

_, err := sr.SigningHandler().CreateSignatureShareForPublicKey(
sigCtx,
headerHash,
uint16(idx),
currentEpoch,
pkBytes,
)
if err != nil {
log.Info("triggerCreateSignaturesForManagedKeys.CreateSignatureShareForPublicKey", "error", err.Error())
return
}
}(sigCtx, pk.idx, pk.pkBytes)
}
}()

log.Debug("step 1: multi keys signatures creation has been triggered", "num", len(keys))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps call cancel()?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or call it on defer after creation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the context was set to be cancelled on signature subround, after waiting with timeout, here we are just triggering the creation

}

func (sr *subroundBlock) getManagedKeysByIndex() []consensusPubKey {
keys := make([]consensusPubKey, 0)
for idx, pk := range sr.ConsensusGroup() {
pkBytes := []byte(pk)
if !sr.IsKeyManagedBySelf(pkBytes) {
continue
}

keys = append(keys, consensusPubKey{idx: idx, pkBytes: pkBytes})
}

return keys
}

func (sr *subroundBlock) sendDirectSentTransactions(
header data.HeaderHandler,
body data.BodyHandler,
Expand Down Expand Up @@ -643,6 +756,8 @@ func (sr *subroundBlock) receivedBlockHeader(headerHandler data.HeaderHandler) {

sr.AddReceivedHeader(headerHandler)

sr.triggerCreateSignaturesForManagedKeys(context.Background(), headerHash, headerHandler)

ctx, cancel := context.WithTimeout(context.Background(), sr.RoundHandler().TimeDuration())
defer cancel()

Expand Down
Loading
Loading