From 400f26912ea6b31cbf511de93c1270776055c758 Mon Sep 17 00:00:00 2001 From: Luc Nguyen Date: Sun, 30 Mar 2025 18:10:12 +0700 Subject: [PATCH 1/7] Persit message, handle failure and timeout sign tx --- cmd/main.go | 28 +++- pkg/event/sign.go | 53 ++++++++ pkg/eventconsumer/consumer.go | 66 +++++++--- pkg/eventconsumer/sign.go | 75 +++++++++++ pkg/eventconsumer/timeout.go | 89 +++++++++++++ pkg/messaging/pubsub.go | 214 ++++++++++++++++++++++++++++++- pkg/mpc/ecdsa_keygen_session.go | 4 +- pkg/mpc/ecdsa_signing_session.go | 37 +++--- pkg/mpc/eddsa_keygen_session.go | 4 +- pkg/mpc/eddsa_signing_session.go | 20 ++- pkg/mpc/node.go | 8 +- pkg/mpc/session.go | 19 ++- 12 files changed, 556 insertions(+), 61 deletions(-) create mode 100644 pkg/event/sign.go create mode 100644 pkg/eventconsumer/sign.go create mode 100644 pkg/eventconsumer/timeout.go diff --git a/cmd/main.go b/cmd/main.go index 345e2bd3..02803d03 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,7 @@ import ( "github.com/cryptoniumX/mpcium/pkg/config" "github.com/cryptoniumX/mpcium/pkg/constant" + "github.com/cryptoniumX/mpcium/pkg/event" "github.com/cryptoniumX/mpcium/pkg/eventconsumer" "github.com/cryptoniumX/mpcium/pkg/infra" "github.com/cryptoniumX/mpcium/pkg/keyinfo" @@ -54,16 +55,26 @@ func main() { defer natsConn.Close() pubsub := messaging.NewNATSPubSub(natsConn) + signingStream, err := messaging.NewJetStreamPubSub(natsConn, event.SigningPublisherStream, []string{ + event.SigningRequestTopic, + }) + if err != nil { + logger.Fatal("Failed to create JetStream PubSub", err) + } + signingCounsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) + signingCounsumer.Run() + defer signingCounsumer.Close() + directMessaging := messaging.NewNatsDirectMessaging(natsConn) mqManager := messaging.NewNATsMessageQueueManager("mpc", []string{ "mpc.mpc_keygen_success.*", - "mpc.mpc_sign_success.*", + event.SigningResultTopic, }, natsConn) genKeySuccessQueue := mqManager.NewMessageQueue("mpc_keygen_success") defer genKeySuccessQueue.Close() - singingSuccessQueue := mqManager.NewMessageQueue("mpc_sign_success") - defer singingSuccessQueue.Close() + singingResultQueue := mqManager.NewMessageQueue("signing_result") + defer singingResultQueue.Close() logger.Info("Node is running", "peerID", nodeID, "name", *nodeName) @@ -85,10 +96,19 @@ func main() { mpcNode, pubsub, genKeySuccessQueue, - singingSuccessQueue, + singingResultQueue, ) eventConsumer.Run() defer eventConsumer.Close() + + timeoutConsumer := eventconsumer.NewTimeOutConsumer( + natsConn, + singingResultQueue, + ) + + timeoutConsumer.Run() + defer timeoutConsumer.Close() + // Create a channel to receive signals signals := make(chan os.Signal, 1) diff --git a/pkg/event/sign.go b/pkg/event/sign.go new file mode 100644 index 00000000..71be3be6 --- /dev/null +++ b/pkg/event/sign.go @@ -0,0 +1,53 @@ +package event + +const ( + SigningPublisherStream = "mpc-signing" + SigningConsumerStream = "mpc-signing-consumer" + SigningRequestTopic = "mpc.signing_request.*" + SigningResultTopic = "mpc.signing_result.*" + SigningResultCompleteTopic = "mpc.signing_result.complete" + MPCSigningEventTopic = "mpc:sign" + SigningRequestEventTopic = "mpc.signing_request.event" +) + +type SigningResultType int + +const ( + SigningResultTypeSuccess SigningResultType = iota + SigningResultTypeError +) + +type SigningResultEvent struct { + ResultType SigningResultType `json:"result_type"` + ErrorReason string `json:"error_reason"` + IsTimeout bool `json:"is_timeout"` + NetworkInternalCode string `json:"network_internal_code"` + WalletID string `json:"wallet_id"` + TxID string `json:"tx_id"` + R []byte `json:"r"` + S []byte `json:"s"` + SignatureRecovery []byte `json:"signature_recovery"` + + // TODO: define two separate events for eddsa and ecdsa + Signature []byte `json:"signature"` +} + +type SigningResultSuccessEvent struct { + NetworkInternalCode string `json:"network_internal_code"` + WalletID string `json:"wallet_id"` + TxID string `json:"tx_id"` + R []byte `json:"r"` + S []byte `json:"s"` + SignatureRecovery []byte `json:"signature_recovery"` + + // TODO: define two separate events for eddsa and ecdsa + Signature []byte `json:"signature"` +} + +type SigningResultErrorEvent struct { + NetworkInternalCode string `json:"network_internal_code"` + WalletID string `json:"wallet_id"` + TxID string `json:"tx_id"` + ErrorReason string `json:"error_reason"` + IsTimeout bool `json:"is_timeout"` +} diff --git a/pkg/eventconsumer/consumer.go b/pkg/eventconsumer/consumer.go index cf17cc82..89eb2271 100644 --- a/pkg/eventconsumer/consumer.go +++ b/pkg/eventconsumer/consumer.go @@ -9,9 +9,11 @@ import ( "sync" "time" + "github.com/cryptoniumX/mpcium/pkg/event" "github.com/cryptoniumX/mpcium/pkg/logger" "github.com/cryptoniumX/mpcium/pkg/messaging" "github.com/cryptoniumX/mpcium/pkg/mpc" + "github.com/nats-io/nats.go" ) const ( @@ -28,8 +30,8 @@ type eventConsumer struct { node *mpc.Node pubsub messaging.PubSub - genKeySucecssQueue messaging.MessageQueue - signingSuccessQueue messaging.MessageQueue + genKeySucecssQueue messaging.MessageQueue + signingResultQueue messaging.MessageQueue keyGenerationSub messaging.Subscription signingSub messaging.Subscription @@ -39,13 +41,13 @@ func NewEventConsumer( node *mpc.Node, pubsub messaging.PubSub, genKeySucecssQueue messaging.MessageQueue, - signingSuccessQueue messaging.MessageQueue, + signingResultQueue messaging.MessageQueue, ) EventConsumer { return &eventConsumer{ - node: node, - pubsub: pubsub, - genKeySucecssQueue: genKeySucecssQueue, - signingSuccessQueue: signingSuccessQueue, + node: node, + pubsub: pubsub, + genKeySucecssQueue: genKeySucecssQueue, + signingResultQueue: signingResultQueue, } } @@ -64,7 +66,8 @@ func (ec *eventConsumer) Run() { } func (ec *eventConsumer) consumeKeyGenerationEvent() error { - sub, err := ec.pubsub.Subscribe(MPCGenerateEvent, func(msg []byte) { + sub, err := ec.pubsub.Subscribe(MPCGenerateEvent, func(natMsg *nats.Msg) { + msg := natMsg.Data walletID := string(msg) // TODO: threshold is configurable threshold := 1 @@ -160,15 +163,16 @@ func (ec *eventConsumer) consumeKeyGenerationEvent() error { } func (ec *eventConsumer) consumeTxSigningEvent() error { - sub, err := ec.pubsub.Subscribe(MPCSignEvent, func(raw []byte) { + sub, err := ec.pubsub.Subscribe(MPCSignEvent, func(natMsg *nats.Msg) { + raw := natMsg.Data var msg SignTxMessage err := json.Unmarshal(raw, &msg) if err != nil { - logger.Error("Failed to unmarshal message", err) + logger.Error("Failed to unmarshal signing message", err) return } - logger.Info("Received signing event", "waleltID", msg.WalletID, "type", msg.KeyType, "tx", msg.Tx) + logger.Info("Received signing event", "waleltID", msg.WalletID, "type", msg.KeyType, "tx", msg.TxID) threshold := 1 var session mpc.ISigningSession @@ -179,7 +183,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.TxID, msg.NetworkInternalCode, threshold, - ec.signingSuccessQueue, + ec.signingResultQueue, ) case KeyTypeEd25519: session, err = ec.node.CreateEDDSASigningSession( @@ -187,20 +191,20 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.TxID, msg.NetworkInternalCode, threshold, - ec.signingSuccessQueue, + ec.signingResultQueue, ) } if err != nil { - logger.Error("Failed to create signing session", err) + ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to create signing session") return } txBigInt := new(big.Int).SetBytes(msg.Tx) err = session.Init(txBigInt) if err != nil { - logger.Error("Failed to init signing session, terminate session", err, "walletID", msg.WalletID) + ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to init signing session") return } @@ -211,7 +215,10 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { case <-ctx.Done(): return case err := <-session.ErrChan(): - logger.Error("Signing session error", err) + if err != nil { + ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to sign tx") + return + } } } }() @@ -219,7 +226,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { session.ListenToIncomingMessageAsync() // TODO: use consul distributed lock here time.Sleep(1 * time.Second) - go session.Sign(done) // use go routine to not block the event susbscriber + go session.Sign(done, natMsg) // use go routine to not block the event susbscriber }) ec.signingSub = sub @@ -230,6 +237,31 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { return nil } +func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkInternalCode string, err error, errMsg string) { + logger.Error("Signing session error", err, "walletID", walletID, "txID", txID, "error", errMsg) + signingResult := event.SigningResultEvent{ + ResultType: event.SigningResultTypeError, + NetworkInternalCode: NetworkInternalCode, + WalletID: walletID, + TxID: txID, + ErrorReason: errMsg, + } + + signingResultBytes, err := json.Marshal(signingResult) + if err != nil { + logger.Error("Failed to marshal signing result event", err) + return + } + + err = ec.signingResultQueue.Enqueue(event.SigningResultCompleteTopic, signingResultBytes, &messaging.EnqueueOptions{ + IdempotententKey: txID, + }) + if err != nil { + logger.Error("Failed to publish signing result event", err) + return + } +} + // Close and clean up func (ec *eventConsumer) Close() error { err := ec.keyGenerationSub.Unsubscribe() diff --git a/pkg/eventconsumer/sign.go b/pkg/eventconsumer/sign.go new file mode 100644 index 00000000..e14e8104 --- /dev/null +++ b/pkg/eventconsumer/sign.go @@ -0,0 +1,75 @@ +package eventconsumer + +import ( + "fmt" + "time" + + "github.com/cryptoniumX/mpcium/pkg/event" + "github.com/cryptoniumX/mpcium/pkg/logger" + "github.com/cryptoniumX/mpcium/pkg/messaging" + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" +) + +type signingConsumer struct { + natsConn *nats.Conn + pubsub messaging.PubSub + jsPubsub messaging.StreamPubsub +} + +func NewSigningConsumer(natsConn *nats.Conn, jsPubsub messaging.StreamPubsub, pubsub messaging.PubSub) *signingConsumer { + return &signingConsumer{ + natsConn: natsConn, + pubsub: pubsub, + jsPubsub: jsPubsub, + } +} + +func (sc *signingConsumer) Run() { + sub, err := sc.jsPubsub.Subscribe(event.SigningConsumerStream, event.SigningRequestEventTopic, func(message jetstream.Msg) { + sc.handleSigningEvent(message) + }) + + if err != nil { + panic(err) + } + defer sub.Unsubscribe() +} + +func (sc *signingConsumer) handleSigningEvent(message jetstream.Msg) { + replyInbox := nats.NewInbox() + sub, err := sc.natsConn.SubscribeSync(replyInbox) + if err != nil { + logger.Error("SigingConsumer: Failed to subscribe to reply inbox", err) + message.Nak() + return + } + defer sub.Unsubscribe() + + sc.pubsub.PublishWithReply(event.MPCSigningEventTopic, replyInbox, message.Data()) + + deadline := time.Now().Add(30 * time.Second) + for time.Now().Before(deadline) { + msg, err := sub.NextMsg(500 * time.Millisecond) + if err != nil { + if err == nats.ErrTimeout { + continue + } + break + } + + if msg != nil { + fmt.Println("SigningConsumer: Completed signing event") + message.Ack() + return + } + } + logger.Warn("SigningConsumer: Timeout waiting for signing event response") + message.Nak() +} + +func (sc *signingConsumer) Close() error { + // Close the consumer and any resources it holds + // For example, you can unsubscribe from the topic or close the connection + return nil +} diff --git a/pkg/eventconsumer/timeout.go b/pkg/eventconsumer/timeout.go new file mode 100644 index 00000000..aa30a306 --- /dev/null +++ b/pkg/eventconsumer/timeout.go @@ -0,0 +1,89 @@ +package eventconsumer + +import ( + "encoding/json" + "fmt" + + "github.com/cryptoniumX/mpcium/pkg/event" + "github.com/cryptoniumX/mpcium/pkg/logger" + "github.com/cryptoniumX/mpcium/pkg/messaging" + "github.com/nats-io/nats.go" +) + +const maxDeliveriesExceededSubject = "$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>" + +type timeOutConsumer struct { + natsConn *nats.Conn + resultQueue messaging.MessageQueue +} + +func NewTimeOutConsumer(natsConn *nats.Conn, resultQueue messaging.MessageQueue) *timeOutConsumer { + return &timeOutConsumer{ + natsConn: natsConn, + resultQueue: resultQueue, + } +} + +func (tc *timeOutConsumer) Run() { + sub, err := tc.natsConn.Subscribe(maxDeliveriesExceededSubject, func(msg *nats.Msg) { + data := msg.Data + var advisory struct { + Stream string `json:"stream"` + StreamSeq uint64 `json:"stream_seq"` + } + + err := json.Unmarshal(data, &advisory) + if err != nil { + logger.Error("Failed to unmarshal advisory message", err) + return + } + + if advisory.Stream != event.SigningPublisherStream { + logger.Info("Ignoring advisory message for non-mpc-signing stream", "stream", advisory.Stream) + js, _ := tc.natsConn.JetStream() + failedMsg, err := js.GetMsg(advisory.Stream, advisory.StreamSeq) + + if err != nil { + logger.Error("Failed to retrieve message", err) + return + } + + data := failedMsg.Data + var signErrorResult event.SigningResultEvent + err = json.Unmarshal(data, &signErrorResult) + + if err != nil { + logger.Error("Failed to unmarshal signing result event", err) + return + } + + signErrorResult.ResultType = event.SigningResultTypeError + signErrorResult.IsTimeout = true + signErrorResult.ErrorReason = fmt.Sprintf("Message delivery exceeded for stream %s", advisory.Stream) + + signErrorResultBytes, err := json.Marshal(signErrorResult) + if err != nil { + logger.Error("Failed to marshal signing result event", err) + return + } + + err = tc.resultQueue.Enqueue(event.SigningResultTopic, signErrorResultBytes, &messaging.EnqueueOptions{ + IdempotententKey: signErrorResult.TxID, + }) + if err != nil { + logger.Error("Failed to publish signing result event", err) + return + } + logger.Info("Published signing result event for timeout", "txID", signErrorResult.TxID) + return + } + }) + if err != nil { + panic(err) + } + defer sub.Unsubscribe() +} + +func (tc *timeOutConsumer) Close() error { + return nil +} diff --git a/pkg/messaging/pubsub.go b/pkg/messaging/pubsub.go index a956ac6b..01e30434 100644 --- a/pkg/messaging/pubsub.go +++ b/pkg/messaging/pubsub.go @@ -1,7 +1,15 @@ package messaging import ( + "context" + "fmt" + "strings" + "time" + "unicode" + + "github.com/cryptoniumX/mpcium/pkg/logger" "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" ) type Subscription interface { @@ -10,7 +18,8 @@ type Subscription interface { type PubSub interface { Publish(topic string, message []byte) error - Subscribe(topic string, handler func(message []byte)) (Subscription, error) + PublishWithReply(ttopic, reply string, data []byte) error + Subscribe(topic string, handler func(msg *nats.Msg)) (Subscription, error) } type natsPubSub struct { @@ -21,23 +30,40 @@ type natsSubscription struct { subscription *nats.Subscription } +type jetstreamSubscription struct { + consumer jetstream.Consumer +} + func (ns *natsSubscription) Unsubscribe() error { return ns.subscription.Unsubscribe() } +func (js *jetstreamSubscription) Unsubscribe() error { + return nil +} + func NewNATSPubSub(natsConn *nats.Conn) PubSub { return &natsPubSub{natsConn} } func (n *natsPubSub) Publish(topic string, message []byte) error { + fmt.Println("Publishing message to topic:", topic) return n.natsConn.Publish(topic, message) } -func (n *natsPubSub) Subscribe(topic string, handler func(message []byte)) (Subscription, error) { +func (n *natsPubSub) PublishWithReply(topic, reply string, data []byte) error { + return n.natsConn.PublishMsg(&nats.Msg{ + Subject: topic, + Reply: reply, + Data: data, + }) +} + +func (n *natsPubSub) Subscribe(topic string, handler func(msg *nats.Msg)) (Subscription, error) { // TODO: Handle subscription // handle more fields in msg sub, err := n.natsConn.Subscribe(topic, func(msg *nats.Msg) { - handler(msg.Data) + handler(msg) }) if err != nil { return nil, err @@ -45,3 +71,185 @@ func (n *natsPubSub) Subscribe(topic string, handler func(message []byte)) (Subs return &natsSubscription{subscription: sub}, nil } + +type StreamPubsub interface { + Publish(topic string, message []byte) error + Subscribe(name string, topic string, handler func(msg jetstream.Msg)) (Subscription, error) +} + +type StreamPubsubOption func(*streamPubSubConfig) + +type streamPubSubConfig struct { + streamName string + subjects []string + description string + retention nats.RetentionPolicy + storage nats.StorageType + maxAge time.Duration + discard nats.DiscardPolicy + ackWait time.Duration + maxDeliveryAttempts int + consumerNamePrefix string +} + +func WithDescription(description string) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.description = description + } +} + +func WithRetention(policy nats.RetentionPolicy) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.retention = policy + } +} + +func WithStorage(storage nats.StorageType) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.storage = storage + } +} + +func WithMaxAge(maxAge time.Duration) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.maxAge = maxAge + } +} + +func WithDiscardPolicy(policy nats.DiscardPolicy) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.discard = policy + } +} + +func WithAckWait(ackWait time.Duration) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.ackWait = ackWait + } +} + +func WithMaxDeliveryAttempts(maxAttempts int) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.maxDeliveryAttempts = maxAttempts + } +} + +func WithConsumerNamePrefix(prefix string) StreamPubsubOption { + return func(cfg *streamPubSubConfig) { + cfg.consumerNamePrefix = prefix + } +} + +type jetStreamPubSub struct { + name string + config streamPubSubConfig + js jetstream.JetStream +} + +func NewJetStreamPubSub(natsConn *nats.Conn, streamName string, subjects []string, opts ...StreamPubsubOption) (StreamPubsub, error) { + config := streamPubSubConfig{ + streamName: streamName, + subjects: subjects, + retention: nats.InterestPolicy, + storage: nats.MemoryStorage, + discard: nats.DiscardOld, + ackWait: 60 * time.Second, + maxDeliveryAttempts: 3, + consumerNamePrefix: "consumer", + } + for _, opt := range opts { + opt(&config) + } + + js, err := jetstream.New(natsConn) + if err != nil { + logger.Fatal("Error creating JetStream context: ", err) + } + + ctx := context.Background() + stream, err := js.Stream(ctx, streamName) + if err != nil { + logger.Warn("Stream not found, creating new stream", "stream", streamName) + } + if stream != nil { + info, _ := stream.Info(ctx) + logger.Info("Stream found", "info", info) + + } + + _, err = js.CreateOrUpdateStream(context.Background(), jetstream.StreamConfig{ + Name: streamName, + Description: "Stream for " + streamName, + Subjects: subjects, + }) + + if err != nil { + logger.Fatal("Error creating JetStream stream: ", err) + } + + logger.Info("Creating apex NATs Jetstream context successfully!") + + return &jetStreamPubSub{ + name: streamName, + config: config, + js: js, + }, nil +} + +func (j *jetStreamPubSub) Publish(topic string, message []byte) error { + _, err := j.js.Publish(context.Background(), topic, message) + return err +} + +func sanitizeConsumerName(name string) string { + // Replace invalid characters + name = strings.ReplaceAll(name, ".", "_") + name = strings.ReplaceAll(name, ":", "_") + name = strings.ReplaceAll(name, " ", "_") + name = strings.ReplaceAll(name, "-", "_") + name = strings.ReplaceAll(name, ">", "all") + name = strings.ReplaceAll(name, "*", "any") + + // Ensure it starts with a letter or underscore + if len(name) > 0 && !unicode.IsLetter(rune(name[0])) && name[0] != '_' { + name = "_" + name + } + + return name +} + +func (j *jetStreamPubSub) Subscribe(name string, topic string, handler func(msg jetstream.Msg)) (Subscription, error) { + + logger.Info("Subscribing to topic", sanitizeConsumerName(name), topic) + consumerConfig := jetstream.ConsumerConfig{ + Name: sanitizeConsumerName(name), + Durable: sanitizeConsumerName(name), + AckPolicy: jetstream.AckExplicitPolicy, + MaxDeliver: 3, + BackOff: []time.Duration{60 * time.Second, 120 * time.Second, 180 * time.Second}, + DeliverPolicy: jetstream.DeliverAllPolicy, // Deliver all messages + FilterSubject: topic, + } + + logger.Info("Creating consumer", "config", consumerConfig, "stream", j.config.streamName) + consumer, err := j.js.CreateOrUpdateConsumer(context.Background(), j.config.streamName, consumerConfig) + + if err != nil { + logger.Error("❌ Failed to create or update consumer:", err) + } + + if consumer != nil { + logger.Info("✅ Successfully created or updated consumer", "consumer", consumer) + } + + _, err = consumer.Consume(func(msg jetstream.Msg) { + logger.Info("Received jetStreamPubSub message", "subject", msg.Data()) + handler(msg) + }) + + if err != nil { + logger.Error("❌ Failed to consume message:", err) + } + + return &jetstreamSubscription{consumer: consumer}, nil +} diff --git a/pkg/mpc/ecdsa_keygen_session.go b/pkg/mpc/ecdsa_keygen_session.go index 15b122f8..ad1dff9e 100644 --- a/pkg/mpc/ecdsa_keygen_session.go +++ b/pkg/mpc/ecdsa_keygen_session.go @@ -40,7 +40,7 @@ func NewKeygenSession( preParams *keygen.LocalPreParams, kvstore kvstore.KVStore, keyinfoStore keyinfo.Store, - successQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) *KeygenSession { return &KeygenSession{ Session: Session{ @@ -68,7 +68,7 @@ func NewKeygenSession( return fmt.Sprintf("ecdsa:%s", walletID) }, getRoundFunc: GetEcdsaMsgRound, - successQueue: successQueue, + resultQueue: resultQueue, sessionType: SessionTypeEcdsa, }, endCh: make(chan *keygen.LocalPartySaveData), diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 402d0b31..740d3ad5 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -11,17 +11,15 @@ import ( "github.com/bnb-chain/tss-lib/v2/ecdsa/signing" "github.com/bnb-chain/tss-lib/v2/tss" "github.com/cryptoniumX/mpcium/pkg/common/errors" + "github.com/cryptoniumX/mpcium/pkg/event" "github.com/cryptoniumX/mpcium/pkg/keyinfo" "github.com/cryptoniumX/mpcium/pkg/kvstore" "github.com/cryptoniumX/mpcium/pkg/logger" "github.com/cryptoniumX/mpcium/pkg/messaging" + "github.com/nats-io/nats.go" "github.com/samber/lo" ) -const ( - SignSuccessTopic = "mpc.mpc_sign_success.completed" -) - // Ecdsa signing session type SigningSession struct { Session @@ -41,19 +39,7 @@ type ISigningSession interface { ISession Init(tx *big.Int) error - Sign(done func()) -} - -type SigningSuccessEvent struct { - NetworkInternalCode string `json:"network_internal_code"` - WalletID string `json:"wallet_id"` - TxID string `json:"tx_id"` - R []byte `json:"r"` - S []byte `json:"s"` - SignatureRecovery []byte `json:"signature_recovery"` - - // TODO: define two separate events for eddsa and ecdsa - Signature []byte `json:"signature"` + Sign(done func(), natMsg *nats.Msg) } func NewSigningSession( @@ -69,7 +55,7 @@ func NewSigningSession( preParams *keygen.LocalPreParams, kvstore kvstore.KVStore, keyinfoStore keyinfo.Store, - succesQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) *SigningSession { return &SigningSession{ Session: Session{ @@ -97,7 +83,7 @@ func NewSigningSession( return fmt.Sprintf("ecdsa:%s", waleltID) }, getRoundFunc: GetEcdsaMsgRound, - successQueue: succesQueue, + resultQueue: resultQueue, }, endCh: make(chan *common.SignatureData), txID: txID, @@ -149,7 +135,7 @@ func (s *SigningSession) Init(tx *big.Int) error { return nil } -func (s *SigningSession) Sign(done func()) { +func (s *SigningSession) Sign(done func(), natMsg *nats.Msg) { logger.Info("Starting signing", "walletID", s.walletID) go func() { if err := s.party.Start(); err != nil { @@ -176,7 +162,8 @@ func (s *SigningSession) Sign(done func()) { return } - r := SigningSuccessEvent{ + r := event.SigningResultEvent{ + ResultType: event.SigningResultTypeSuccess, NetworkInternalCode: s.networkInternalCode, WalletID: s.walletID, TxID: s.txID, @@ -191,7 +178,7 @@ func (s *SigningSession) Sign(done func()) { return } - err = s.successQueue.Enqueue(SignSuccessTopic, bytes, &messaging.EnqueueOptions{ + err = s.resultQueue.Enqueue(event.SigningResultCompleteTopic, bytes, &messaging.EnqueueOptions{ IdempotententKey: s.txID, }) if err != nil { @@ -200,6 +187,12 @@ func (s *SigningSession) Sign(done func()) { return } + //Reply to the original message + if natMsg.Reply != "" { + _ = s.Session.pubSub.Publish(natMsg.Reply, bytes) + logger.Info("Reply to the original message", "reply", natMsg.Reply) + } + logger.Info("[SIGN] Sign successfully", "walletID", s.walletID) err = s.Close() if err != nil { diff --git a/pkg/mpc/eddsa_keygen_session.go b/pkg/mpc/eddsa_keygen_session.go index 0bccff46..2a6152df 100644 --- a/pkg/mpc/eddsa_keygen_session.go +++ b/pkg/mpc/eddsa_keygen_session.go @@ -33,7 +33,7 @@ func NewEDDSAKeygenSession( threshold int, kvstore kvstore.KVStore, keyinfoStore keyinfo.Store, - successQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) *EDDSAKeygenSession { return &EDDSAKeygenSession{Session: Session{ walletID: walletID, @@ -59,7 +59,7 @@ func NewEDDSAKeygenSession( return fmt.Sprintf("eddsa:%s", waleltID) }, getRoundFunc: GetEddsaMsgRound, - successQueue: successQueue, + resultQueue: resultQueue, sessionType: SessionTypeEddsa, }, endCh: make(chan *keygen.LocalPartySaveData), diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index 95e26e48..f7cbd019 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -10,11 +10,13 @@ import ( "github.com/bnb-chain/tss-lib/v2/eddsa/signing" "github.com/bnb-chain/tss-lib/v2/tss" "github.com/cryptoniumX/mpcium/pkg/common/errors" + "github.com/cryptoniumX/mpcium/pkg/event" "github.com/cryptoniumX/mpcium/pkg/keyinfo" "github.com/cryptoniumX/mpcium/pkg/kvstore" "github.com/cryptoniumX/mpcium/pkg/logger" "github.com/cryptoniumX/mpcium/pkg/messaging" "github.com/decred/dcrd/dcrec/edwards/v2" + "github.com/nats-io/nats.go" "github.com/samber/lo" ) @@ -39,7 +41,7 @@ func NewEDDSASigningSession( threshold int, kvstore kvstore.KVStore, keyinfoStore keyinfo.Store, - succesQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) *EDDSASigningSession { return &EDDSASigningSession{ Session: Session{ @@ -67,7 +69,7 @@ func NewEDDSASigningSession( return fmt.Sprintf("eddsa:%s", waleltID) }, getRoundFunc: GetEddsaMsgRound, - successQueue: succesQueue, + resultQueue: resultQueue, }, endCh: make(chan *common.SignatureData), txID: txID, @@ -119,7 +121,7 @@ func (s *EDDSASigningSession) Init(tx *big.Int) error { return nil } -func (s *EDDSASigningSession) Sign(done func()) { +func (s *EDDSASigningSession) Sign(done func(), natMsg *nats.Msg) { logger.Info("Starting signing", "walletID", s.walletID) go func() { if err := s.party.Start(); err != nil { @@ -146,7 +148,8 @@ func (s *EDDSASigningSession) Sign(done func()) { return } - r := SigningSuccessEvent{ + r := event.SigningResultEvent{ + ResultType: event.SigningResultTypeSuccess, NetworkInternalCode: s.networkInternalCode, WalletID: s.walletID, TxID: s.txID, @@ -159,7 +162,7 @@ func (s *EDDSASigningSession) Sign(done func()) { return } - err = s.successQueue.Enqueue(SignSuccessTopic, bytes, &messaging.EnqueueOptions{ + err = s.resultQueue.Enqueue(event.SigningResultCompleteTopic, bytes, &messaging.EnqueueOptions{ IdempotententKey: s.txID, }) if err != nil { @@ -168,6 +171,13 @@ func (s *EDDSASigningSession) Sign(done func()) { } logger.Info("[SIGN] Sign successfully", "walletID", s.walletID) + + //Reply to the original message + if natMsg.Reply != "" { + _ = s.Session.pubSub.Publish(natMsg.Reply, bytes) + logger.Info("Reply to the original message", "reply", natMsg.Reply) + } + err = s.Close() if err != nil { logger.Error("Failed to close session", err) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index 468c0a2b..10a647da 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -141,7 +141,7 @@ func (p *Node) CreateSigningSession( txID string, networkInternalCode string, threshold int, - successQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) (*SigningSession, error) { readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) @@ -158,7 +158,7 @@ func (p *Node) CreateSigningSession( p.ecdsaPreParams, p.kvstore, p.keyinfoStore, - successQueue, + resultQueue, ) return session, nil } @@ -168,7 +168,7 @@ func (p *Node) CreateEDDSASigningSession( txID string, networkInternalCode string, threshold int, - successQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) (*EDDSASigningSession, error) { readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) @@ -184,7 +184,7 @@ func (p *Node) CreateEDDSASigningSession( threshold, p.kvstore, p.keyinfoStore, - successQueue, + resultQueue, ) return session, nil } diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index 6e24914e..34fdfa63 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -12,6 +12,7 @@ import ( "github.com/cryptoniumX/mpcium/pkg/kvstore" "github.com/cryptoniumX/mpcium/pkg/logger" "github.com/cryptoniumX/mpcium/pkg/messaging" + "github.com/nats-io/nats.go" ) type TopicComposer struct { @@ -47,7 +48,7 @@ type Session struct { keyinfoStore keyinfo.Store broadcastSub messaging.Subscription directSub messaging.Subscription - successQueue messaging.MessageQueue + resultQueue messaging.MessageQueue topicComposer *TopicComposer composeKey KeyComposerFn @@ -136,9 +137,23 @@ func (s *Session) receiveTssMessage(rawMsg []byte) { } } +func (s *Session) SendReplySignSuccess(natMsg *nats.Msg) { + msg := natMsg.Data + s.mu.Lock() + defer s.mu.Unlock() + + err := s.pubSub.Publish(natMsg.Reply, msg) + if err != nil { + s.ErrCh <- fmt.Errorf("Failed to reply sign sucess message: %w", err) + return + } + logger.Info("Sent reply sign sucess message", "reply", natMsg.Reply) +} + func (s *Session) ListenToIncomingMessageAsync() { go func() { - sub, err := s.pubSub.Subscribe(s.topicComposer.ComposeBroadcastTopic(), func(msg []byte) { + sub, err := s.pubSub.Subscribe(s.topicComposer.ComposeBroadcastTopic(), func(natMsg *nats.Msg) { + msg := natMsg.Data s.receiveTssMessage(msg) }) From e8ffa381f489a83e60dbcbf5262927e99eca2382 Mon Sep 17 00:00:00 2001 From: Luc Nguyen Date: Sun, 30 Mar 2025 21:11:47 +0700 Subject: [PATCH 2/7] Update signing timeout logic when not enough participants --- pkg/eventconsumer/consumer.go | 17 +++++++++++++---- pkg/eventconsumer/timeout.go | 8 +++++--- pkg/messaging/pubsub.go | 2 +- pkg/mpc/ecdsa_signing_session.go | 4 +++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/pkg/eventconsumer/consumer.go b/pkg/eventconsumer/consumer.go index 89eb2271..a25e86d2 100644 --- a/pkg/eventconsumer/consumer.go +++ b/pkg/eventconsumer/consumer.go @@ -197,14 +197,18 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { } if err != nil { - ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to create signing session") + ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to create signing session", natMsg) return } txBigInt := new(big.Int).SetBytes(msg.Tx) err = session.Init(txBigInt) if err != nil { - ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to init signing session") + if err.Error() == "Not enough participants to sign" { + //Return for retry later + return + } + ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to init signing session", natMsg) return } @@ -216,7 +220,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { return case err := <-session.ErrChan(): if err != nil { - ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to sign tx") + ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to sign tx", natMsg) return } } @@ -237,7 +241,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { return nil } -func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkInternalCode string, err error, errMsg string) { +func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkInternalCode string, err error, errMsg string, natMsg *nats.Msg) { logger.Error("Signing session error", err, "walletID", walletID, "txID", txID, "error", errMsg) signingResult := event.SigningResultEvent{ ResultType: event.SigningResultTypeError, @@ -253,6 +257,11 @@ func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkIntern return } + if natMsg.Reply != "" { + _ = ec.pubsub.Publish(natMsg.Reply, signingResultBytes) + logger.Info("Reply to the original message", "reply", natMsg.Reply) + } + err = ec.signingResultQueue.Enqueue(event.SigningResultCompleteTopic, signingResultBytes, &messaging.EnqueueOptions{ IdempotententKey: txID, }) diff --git a/pkg/eventconsumer/timeout.go b/pkg/eventconsumer/timeout.go index aa30a306..8b0bc124 100644 --- a/pkg/eventconsumer/timeout.go +++ b/pkg/eventconsumer/timeout.go @@ -37,9 +37,10 @@ func (tc *timeOutConsumer) Run() { logger.Error("Failed to unmarshal advisory message", err) return } + logger.Info("Received advisory message", "stream", advisory.Stream, "stream_seq", advisory.StreamSeq) - if advisory.Stream != event.SigningPublisherStream { - logger.Info("Ignoring advisory message for non-mpc-signing stream", "stream", advisory.Stream) + if advisory.Stream == event.SigningPublisherStream { + logger.Info("Received max deliveries exceeded advisory", "stream", advisory.Stream, "stream_seq", advisory.StreamSeq) js, _ := tc.natsConn.JetStream() failedMsg, err := js.GetMsg(advisory.Stream, advisory.StreamSeq) @@ -79,7 +80,8 @@ func (tc *timeOutConsumer) Run() { } }) if err != nil { - panic(err) + logger.Error("Failed to subscribe to max deliveries exceeded subject", err) + return } defer sub.Unsubscribe() } diff --git a/pkg/messaging/pubsub.go b/pkg/messaging/pubsub.go index 01e30434..9f3a9c78 100644 --- a/pkg/messaging/pubsub.go +++ b/pkg/messaging/pubsub.go @@ -226,7 +226,7 @@ func (j *jetStreamPubSub) Subscribe(name string, topic string, handler func(msg Durable: sanitizeConsumerName(name), AckPolicy: jetstream.AckExplicitPolicy, MaxDeliver: 3, - BackOff: []time.Duration{60 * time.Second, 120 * time.Second, 180 * time.Second}, + BackOff: []time.Duration{60 * time.Second, 60 * time.Second, 60 * time.Second}, DeliverPolicy: jetstream.DeliverAllPolicy, // Deliver all messages FilterSubject: topic, } diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 740d3ad5..76c95e91 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -107,7 +107,9 @@ func (s *SigningSession) Init(tx *big.Int) error { } if len(s.participantPeerIDs) < keyInfo.Threshold+1 { - return fmt.Errorf("Not enough participants to sign, expected %d, got %d", keyInfo.Threshold+1, len(s.participantPeerIDs)) + err := fmt.Errorf("Not enough participants to sign") + logger.Warn("Not enough participants to sign", "participants", s.participantPeerIDs, "expected", keyInfo.Threshold+1) + return err } // check if t+1 participants are present From 52ee83c3ecc2bbb8c16a8227f4f00b72a57c8499 Mon Sep 17 00:00:00 2001 From: Luc Nguyen Date: Mon, 31 Mar 2025 15:59:18 +0700 Subject: [PATCH 3/7] Update timeout consumer keep subscribe on time --- cmd/main.go | 7 ++++--- pkg/eventconsumer/timeout.go | 10 +++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 02803d03..6690ad22 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -61,9 +61,6 @@ func main() { if err != nil { logger.Fatal("Failed to create JetStream PubSub", err) } - signingCounsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) - signingCounsumer.Run() - defer signingCounsumer.Close() directMessaging := messaging.NewNatsDirectMessaging(natsConn) mqManager := messaging.NewNATsMessageQueueManager("mpc", []string{ @@ -109,6 +106,10 @@ func main() { timeoutConsumer.Run() defer timeoutConsumer.Close() + signingCounsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) + signingCounsumer.Run() + defer signingCounsumer.Close() + // Create a channel to receive signals signals := make(chan os.Signal, 1) diff --git a/pkg/eventconsumer/timeout.go b/pkg/eventconsumer/timeout.go index 8b0bc124..6c4a1802 100644 --- a/pkg/eventconsumer/timeout.go +++ b/pkg/eventconsumer/timeout.go @@ -10,11 +10,13 @@ import ( "github.com/nats-io/nats.go" ) +// Other service not listen to this subject that make loss of message const maxDeliveriesExceededSubject = "$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>" type timeOutConsumer struct { natsConn *nats.Conn resultQueue messaging.MessageQueue + advisorySub messaging.Subscription } func NewTimeOutConsumer(natsConn *nats.Conn, resultQueue messaging.MessageQueue) *timeOutConsumer { @@ -25,6 +27,7 @@ func NewTimeOutConsumer(natsConn *nats.Conn, resultQueue messaging.MessageQueue) } func (tc *timeOutConsumer) Run() { + logger.Info("Starting advisory consumer for max deliveries exceeded") sub, err := tc.natsConn.Subscribe(maxDeliveriesExceededSubject, func(msg *nats.Msg) { data := msg.Data var advisory struct { @@ -83,9 +86,14 @@ func (tc *timeOutConsumer) Run() { logger.Error("Failed to subscribe to max deliveries exceeded subject", err) return } - defer sub.Unsubscribe() + + tc.advisorySub = sub } func (tc *timeOutConsumer) Close() error { + err := tc.advisorySub.Unsubscribe() + if err != nil { + return err + } return nil } From e79f6e20fbe225e5aad8b0c9e70578356fce9573 Mon Sep 17 00:00:00 2001 From: anhthii Date: Wed, 2 Apr 2025 19:28:28 +0700 Subject: [PATCH 4/7] Handle duplicate message --- cmd/main.go | 5 +- pkg/eventconsumer/consumer.go | 96 ++++++++++++++++++++++++++++++++++- pkg/messaging/pubsub.go | 1 + pkg/mpc/node.go | 1 - 4 files changed, 100 insertions(+), 3 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 6690ad22..e6088ff9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -105,8 +105,11 @@ func main() { timeoutConsumer.Run() defer timeoutConsumer.Close() - signingCounsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) + + // Make the node ready before starting the signing consumer + peerRegistry.Ready() + signingCounsumer.Run() defer signingCounsumer.Close() diff --git a/pkg/eventconsumer/consumer.go b/pkg/eventconsumer/consumer.go index a25e86d2..a925fe73 100644 --- a/pkg/eventconsumer/consumer.go +++ b/pkg/eventconsumer/consumer.go @@ -35,6 +35,13 @@ type eventConsumer struct { keyGenerationSub messaging.Subscription signingSub messaging.Subscription + + // Track active sessions with timestamps for cleanup + activeSessions map[string]time.Time // Maps "walletID-txID" to creation time + sessionsLock sync.RWMutex + cleanupInterval time.Duration // How often to run cleanup + sessionTimeout time.Duration // How long before a session is considered stale + cleanupStopChan chan struct{} // Signal to stop cleanup goroutine } func NewEventConsumer( @@ -43,12 +50,21 @@ func NewEventConsumer( genKeySucecssQueue messaging.MessageQueue, signingResultQueue messaging.MessageQueue, ) EventConsumer { - return &eventConsumer{ + ec := &eventConsumer{ node: node, pubsub: pubsub, genKeySucecssQueue: genKeySucecssQueue, signingResultQueue: signingResultQueue, + activeSessions: make(map[string]time.Time), + cleanupInterval: 5 * time.Minute, // Run cleanup every 5 minutes + sessionTimeout: 30 * time.Minute, // Consider sessions older than 30 minutes stale + cleanupStopChan: make(chan struct{}), } + + // Start background cleanup goroutine + go ec.sessionCleanupRoutine() + + return ec } func (ec *eventConsumer) Run() { @@ -175,6 +191,11 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { logger.Info("Received signing event", "waleltID", msg.WalletID, "type", msg.KeyType, "tx", msg.TxID) threshold := 1 + // Check for duplicate session and track if new + if ec.checkDuplicateSession(msg.WalletID, msg.TxID) { + return + } + var session mpc.ISigningSession switch msg.KeyType { case KeyTypeSecp256k1: @@ -196,6 +217,8 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { } + ec.addSession(msg.WalletID, msg.TxID) + if err != nil { ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to create signing session", natMsg) return @@ -271,8 +294,79 @@ func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkIntern } } +// Add a cleanup routine that runs periodically +func (ec *eventConsumer) sessionCleanupRoutine() { + ticker := time.NewTicker(ec.cleanupInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + ec.cleanupStaleSessions() + case <-ec.cleanupStopChan: + return + } + } +} + +// Cleanup stale sessions +func (ec *eventConsumer) cleanupStaleSessions() { + now := time.Now() + ec.sessionsLock.Lock() + defer ec.sessionsLock.Unlock() + + for sessionID, creationTime := range ec.activeSessions { + if now.Sub(creationTime) > ec.sessionTimeout { + logger.Info("Cleaning up stale session", "sessionID", sessionID, "age", now.Sub(creationTime)) + delete(ec.activeSessions, sessionID) + } + } +} + +// markSessionAsActive marks a session as active with the current timestamp +func (ec *eventConsumer) addSession(walletID, txID string) { + sessionID := fmt.Sprintf("%s-%s", walletID, txID) + ec.sessionsLock.Lock() + ec.activeSessions[sessionID] = time.Now() + ec.sessionsLock.Unlock() +} + +// Remove a session from tracking +func (ec *eventConsumer) removeSession(walletID, txID string) { + sessionID := fmt.Sprintf("%s-%s", walletID, txID) + ec.sessionsLock.Lock() + delete(ec.activeSessions, sessionID) + ec.sessionsLock.Unlock() +} + +// checkAndTrackSession checks if a session already exists and tracks it if new. +// Returns true if the session is a duplicate. +func (ec *eventConsumer) checkDuplicateSession(walletID, txID string) bool { + sessionID := fmt.Sprintf("%s-%s", walletID, txID) + + // Check for duplicate + ec.sessionsLock.RLock() + _, isDuplicate := ec.activeSessions[sessionID] + ec.sessionsLock.RUnlock() + + if isDuplicate { + logger.Info("Duplicate signing request detected", "walletID", walletID, "txID", txID) + return true + } + + // Mark as active + ec.sessionsLock.Lock() + ec.activeSessions[sessionID] = time.Now() + ec.sessionsLock.Unlock() + + return false +} + // Close and clean up func (ec *eventConsumer) Close() error { + // Signal cleanup routine to stop + close(ec.cleanupStopChan) + err := ec.keyGenerationSub.Unsubscribe() if err != nil { return err diff --git a/pkg/messaging/pubsub.go b/pkg/messaging/pubsub.go index 9f3a9c78..f8fb2218 100644 --- a/pkg/messaging/pubsub.go +++ b/pkg/messaging/pubsub.go @@ -229,6 +229,7 @@ func (j *jetStreamPubSub) Subscribe(name string, topic string, handler func(msg BackOff: []time.Duration{60 * time.Second, 60 * time.Second, 60 * time.Second}, DeliverPolicy: jetstream.DeliverAllPolicy, // Deliver all messages FilterSubject: topic, + AckWait: 30 * time.Second, // explicitly set ack wait here } logger.Info("Creating consumer", "config", consumerConfig, "stream", j.config.streamName) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index 10a647da..e9041854 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -68,7 +68,6 @@ func NewNode( } logger.Info("Starting new node, preparams is generated successfully!") - peerRegistry.Ready() go peerRegistry.WatchPeersReady() return &Node{ From a9192ca11581dd986bdd21728cbda4b78d75a753 Mon Sep 17 00:00:00 2001 From: anhthii Date: Wed, 2 Apr 2025 21:21:32 +0700 Subject: [PATCH 5/7] Fix bug signing doesn't work after all nodes are backup --- pkg/eventconsumer/consumer.go | 29 ++++++++++++++--------------- pkg/logger/logger.go | 2 +- pkg/messaging/pubsub.go | 2 +- pkg/mpc/ecdsa_signing_session.go | 3 +-- pkg/mpc/eddsa_signing_session.go | 3 ++- pkg/mpc/session.go | 4 ++++ 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/pkg/eventconsumer/consumer.go b/pkg/eventconsumer/consumer.go index a925fe73..b1de2449 100644 --- a/pkg/eventconsumer/consumer.go +++ b/pkg/eventconsumer/consumer.go @@ -3,6 +3,7 @@ package eventconsumer import ( "context" "encoding/json" + "errors" "fmt" "log" "math/big" @@ -145,9 +146,6 @@ func (ec *eventConsumer) consumeKeyGenerationEvent() error { go eddsaSession.GenerateKey(doneEddsa) wg.Wait() - if err != nil { - logger.Error("Errors when closing sessions", err) - } logger.Info("Closing section successfully!", "event", successEvent) successEventBytes, err := json.Marshal(successEvent) @@ -165,9 +163,6 @@ func (ec *eventConsumer) consumeKeyGenerationEvent() error { } logger.Info("[COMPLETED KEY GEN] Key generation completed successfully", "walletID", walletID) - if err != nil { - logger.Error("Failed to close session", err) - } }) @@ -193,6 +188,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { // Check for duplicate session and track if new if ec.checkDuplicateSession(msg.WalletID, msg.TxID) { + natMsg.Term() return } @@ -217,8 +213,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { } - ec.addSession(msg.WalletID, msg.TxID) - if err != nil { ec.handleSigningSessionError(msg.WalletID, msg.TxID, msg.NetworkInternalCode, err, "Failed to create signing session", natMsg) return @@ -227,7 +221,8 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { txBigInt := new(big.Int).SetBytes(msg.Tx) err = session.Init(txBigInt) if err != nil { - if err.Error() == "Not enough participants to sign" { + if errors.Is(err, mpc.ErrNotEnoughParticipants) { + logger.Info("RETRY LATER: Not enough participants to sign") //Return for retry later return } @@ -235,6 +230,9 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { return } + // Mark session as already processed + ec.addSession(msg.WalletID, msg.TxID) + ctx, done := context.WithCancel(context.Background()) go func() { for { @@ -251,7 +249,13 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { }() session.ListenToIncomingMessageAsync() - // TODO: use consul distributed lock here + // TODO: use consul distributed lock here, only sign after all nodes has already completed listing to incoming message async + // The purpose of the sleep is to be ensuring that the node has properly set up its message listeners + // before it starts the signing process. If the signing process starts sending messages before other nodes + // have set up their listeners, those messages might be missed, potentially causing the signing process to fail. + // One solution: + // The messaging includes mechanisms for direct point-to-point communication (in point2point.go). + // The nodes could explicitly coordinate through request-response patterns before starting signing time.Sleep(1 * time.Second) go session.Sign(done, natMsg) // use go routine to not block the event susbscriber }) @@ -354,11 +358,6 @@ func (ec *eventConsumer) checkDuplicateSession(walletID, txID string) bool { return true } - // Mark as active - ec.sessionsLock.Lock() - ec.activeSessions[sessionID] = time.Now() - ec.sessionsLock.Unlock() - return false } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index dd42bb6c..543c314a 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -12,7 +12,7 @@ var Log zerolog.Logger func Init(env string) { zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack if env != "production" { - Log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, NoColor: false}).With().Timestamp().Logger() + Log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, NoColor: false, TimeFormat: "2006-01-02 15:04:05.000"}).With().Timestamp().Logger() } else { Log = zerolog.New(os.Stdout).With().Timestamp().Logger() } diff --git a/pkg/messaging/pubsub.go b/pkg/messaging/pubsub.go index f8fb2218..ab4f4d0f 100644 --- a/pkg/messaging/pubsub.go +++ b/pkg/messaging/pubsub.go @@ -226,7 +226,7 @@ func (j *jetStreamPubSub) Subscribe(name string, topic string, handler func(msg Durable: sanitizeConsumerName(name), AckPolicy: jetstream.AckExplicitPolicy, MaxDeliver: 3, - BackOff: []time.Duration{60 * time.Second, 60 * time.Second, 60 * time.Second}, + BackOff: []time.Duration{30 * time.Second, 30 * time.Second, 30 * time.Second}, DeliverPolicy: jetstream.DeliverAllPolicy, // Deliver all messages FilterSubject: topic, AckWait: 30 * time.Second, // explicitly set ack wait here diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 76c95e91..051a3a82 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -107,9 +107,8 @@ func (s *SigningSession) Init(tx *big.Int) error { } if len(s.participantPeerIDs) < keyInfo.Threshold+1 { - err := fmt.Errorf("Not enough participants to sign") logger.Warn("Not enough participants to sign", "participants", s.participantPeerIDs, "expected", keyInfo.Threshold+1) - return err + return ErrNotEnoughParticipants } // check if t+1 participants are present diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index f7cbd019..efb6449d 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -93,7 +93,8 @@ func (s *EDDSASigningSession) Init(tx *big.Int) error { } if len(s.participantPeerIDs) < keyInfo.Threshold+1 { - return fmt.Errorf("Not enough participants to sign, expected %d, got %d", keyInfo.Threshold+1, len(s.participantPeerIDs)) + logger.Warn("Not enough participants to sign, expected %d, got %d", keyInfo.Threshold+1, len(s.participantPeerIDs)) + return ErrNotEnoughParticipants } // check if t+1 participants are present diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index 34fdfa63..7a62e342 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -15,6 +15,10 @@ import ( "github.com/nats-io/nats.go" ) +var ( + ErrNotEnoughParticipants = errors.New("Not enough participants to sign") +) + type TopicComposer struct { ComposeBroadcastTopic func() string ComposeDirectTopic func(nodeID string) string From 9602d4d9bfe37c2d038856d3ed206bfecd2e8c93 Mon Sep 17 00:00:00 2001 From: anhthii Date: Sat, 12 Apr 2025 16:21:28 +0700 Subject: [PATCH 6/7] Use onSuccess callback when sign session succeeds --- README.md | 4 + cmd/main.go | 27 ++-- images/diagram.png | Bin 0 -> 99922 bytes pkg/event/sign.go | 3 +- .../{consumer.go => event_consumer.go} | 59 +++++-- pkg/eventconsumer/sign.go | 75 --------- pkg/eventconsumer/sign_consumer.go | 144 ++++++++++++++++++ .../{timeout.go => timeout_consumer.go} | 0 pkg/mpc/ecdsa_signing_session.go | 13 +- pkg/mpc/eddsa_signing_session.go | 11 +- 10 files changed, 219 insertions(+), 117 deletions(-) create mode 100644 images/diagram.png rename pkg/eventconsumer/{consumer.go => event_consumer.go} (90%) delete mode 100644 pkg/eventconsumer/sign.go create mode 100644 pkg/eventconsumer/sign_consumer.go rename pkg/eventconsumer/{timeout.go => timeout_consumer.go} (100%) diff --git a/README.md b/README.md index e075abc4..4758e25f 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,7 @@ $ go run cmd/main.go --name=mpcium1 $ go run cmd/main.go --name=mpcium2 ``` + +### Diagaram + +![Diagram](images/diagram.png) diff --git a/cmd/main.go b/cmd/main.go index e6088ff9..58d0961c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "os" @@ -105,22 +106,24 @@ func main() { timeoutConsumer.Run() defer timeoutConsumer.Close() - signingCounsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) + signingConsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) // Make the node ready before starting the signing consumer peerRegistry.Ready() - signingCounsumer.Run() - defer signingCounsumer.Close() - - // Create a channel to receive signals - - signals := make(chan os.Signal, 1) - signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) - - // Block the execution until a signal is received - <-signals - + ctx, cancel := context.WithCancel(context.Background()) + // Setup signal handling to cancel context on termination signals. + go func() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + <-sigChan + logger.Warn("Shutdown signal received, canceling context...") + cancel() + }() + + if err := signingConsumer.Run(ctx); err != nil { + logger.Error("error running consumer:", err) + } } func NewConsulClient(addr string) *api.Client { diff --git a/images/diagram.png b/images/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..e752bfdecbfaa0beb9ca89162ee47decc0a88907 GIT binary patch literal 99922 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 kA7_k)E*W6_6PjYkN|BCy7SDeO9zBWX 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%0D0%9j#hJy{d6nhXz}QHv9i>Abi*gll$JD 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

#zAdaZQ{9 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 z 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