@@ -26,6 +26,9 @@ const (
2626 MPCReshareEvent = "mpc:reshare"
2727 MPCPresignEvent = "mpc:presign"
2828
29+ // Internal event to notify presign pool of a hot wallet
30+ MPCHotWalletEvent = "mpc:wallet_hot"
31+
2932 DefaultConcurrentKeygen = 2
3033 DefaultConcurrentSigning = 20
3134 DefaultSessionWarmUpDelay = 200
@@ -66,6 +69,12 @@ type eventConsumer struct {
6669 cleanupInterval time.Duration // How often to run cleanup
6770 sessionTimeout time.Duration // How long before a session is considered stale
6871 cleanupStopChan chan struct {} // Signal to stop cleanup goroutine
72+
73+ // Track recent signing activity to detect hot wallets
74+ hotMu sync.Mutex
75+ recentSigns map [string ][]time.Time // key: walletID|keyType|protocol → timestamps within window
76+ hotWindow time.Duration // window for counting signs (e.g., 5 minutes)
77+ hotThreshold int // signs needed to mark as hot
6978}
7079
7180func NewEventConsumer (
@@ -120,6 +129,9 @@ func NewEventConsumer(
120129 keygenMsgBuffer : make (chan * nats.Msg , 100 ),
121130 signingMsgBuffer : make (chan * nats.Msg , 200 ), // Larger buffer for signing
122131 sessionWarmUpDelayMs : sessionWarmUpDelayMs ,
132+ recentSigns : make (map [string ][]time.Time ),
133+ hotWindow : 5 * time .Minute ,
134+ hotThreshold : 2 ,
123135 }
124136
125137 go ec .startKeyGenEventWorker ()
@@ -393,6 +405,9 @@ func (ec *eventConsumer) handleSigningEvent(natMsg *nats.Msg) {
393405 ec .node .ID (),
394406 )
395407
408+ // Track activity to detect hot wallets
409+ ec .trackAndMaybeNotifyHot (msg )
410+
396411 // Check for duplicate session and track if new
397412 if ec .checkDuplicateSession (msg .WalletID , msg .TxID ) {
398413 duplicateErr := fmt .Errorf (
@@ -658,7 +673,7 @@ func (ec *eventConsumer) consumePresignEvent() error {
658673 }
659674
660675 if success {
661- ec .handlePresignSessionSuccess (msg .WalletID , natMsg )
676+ ec .handlePresignSessionSuccess (msg .WalletID , msg . TxID , natMsg )
662677 } else {
663678 ec .handlePresignSessionError (msg .WalletID ,
664679 fmt .Errorf ("presign operation returned false" ),
@@ -676,10 +691,11 @@ func (ec *eventConsumer) consumePresignEvent() error {
676691}
677692
678693// handlePresignSessionSuccess handles successful presign operations
679- func (ec * eventConsumer ) handlePresignSessionSuccess (walletID string , natMsg * nats.Msg ) {
694+ func (ec * eventConsumer ) handlePresignSessionSuccess (walletID string , txID string , natMsg * nats.Msg ) {
680695 presignResult := event.PresignResultEvent {
681696 ResultType : event .ResultTypeSuccess ,
682697 WalletID : walletID ,
698+ TxID : txID ,
683699 Status : "success" ,
684700 }
685701
@@ -863,3 +879,38 @@ func composeSigningIdempotentKey(txID string, natMsg *nats.Msg) string {
863879func composeReshareIdempotentKey (sessionID string , natMsg * nats.Msg ) string {
864880 return composeIdempotentKey (sessionID , natMsg , mpc .TypeReshareWalletResultFmt )
865881}
882+
883+ // trackAndMaybeNotifyHot records a signing event and publishes a hot wallet event
884+ // if at least hotThreshold signs occur within hotWindow for the same
885+ // (walletID, keyType, protocol) tuple.
886+ func (ec * eventConsumer ) trackAndMaybeNotifyHot (msg types.SignTxMessage ) {
887+ if msg .Protocol != types .ProtocolCGGMP21 {
888+ return
889+ }
890+ key := fmt .Sprintf ("%s:%s:%s" , msg .WalletID , string (msg .KeyType ), string (msg .Protocol ))
891+ now := time .Now ()
892+
893+ ec .hotMu .Lock ()
894+ // prune old entries
895+ list := ec .recentSigns [key ]
896+ pruned := list [:0 ]
897+ cutoff := now .Add (- ec .hotWindow )
898+ for _ , t := range list {
899+ if t .After (cutoff ) {
900+ pruned = append (pruned , t )
901+ }
902+ }
903+
904+ ec .recentSigns [key ] = append ([]time.Time (nil ), pruned ... )
905+ currentCount := len (ec .recentSigns [key ])
906+
907+ // If this push reaches the threshold, publish hot wallet once
908+ shouldPublish := currentCount + 1 == ec .hotThreshold
909+ ec .recentSigns [key ] = append (ec .recentSigns [key ], now )
910+ ec .hotMu .Unlock ()
911+
912+ if shouldPublish {
913+ _ = ec .pubsub .Publish (MPCHotWalletEvent , []byte (msg .WalletID ))
914+ logger .Info ("Published hot wallet event" , "walletID" , msg .WalletID )
915+ }
916+ }
0 commit comments