From e862d582b743cd2deb7a942d406b9be4779953b6 Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Tue, 5 May 2026 16:26:34 +0800 Subject: [PATCH 1/8] - fix setup.sh by adding PATH --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index 7be28d39..37ad034e 100755 --- a/setup.sh +++ b/setup.sh @@ -1,3 +1,5 @@ +export PATH="$(go env GOPATH)/bin:$PATH" + NUM_NODES=3 make build From ba6e145cad4d10ac8767cca636fd5fb7ddbc044d Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Tue, 5 May 2026 16:27:13 +0800 Subject: [PATCH 2/8] - fix `Badger Password` error with e2e tests --- e2e/base_test.go | 16 +++++++++++++++- e2e/setup_test_identities.sh | 6 +++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/e2e/base_test.go b/e2e/base_test.go index 194b5f1b..1e68de25 100644 --- a/e2e/base_test.go +++ b/e2e/base_test.go @@ -80,7 +80,20 @@ func (s *E2ETestSuite) LoadConfig() error { return err } - return yaml.Unmarshal(data, &s.config) + if err := yaml.Unmarshal(data, &s.config); err != nil { + return err + } + + return validateBadgerEncryptionKey(s.config.BadgerPassword) +} + +func validateBadgerEncryptionKey(key string) error { + switch len([]byte(key)) { + case 16, 24, 32: + return nil + default: + return fmt.Errorf("badger_password must be 16, 24, or 32 bytes for Badger encryption, got %d bytes", len([]byte(key))) + } } func (s *E2ETestSuite) RunMakeClean() error { @@ -284,6 +297,7 @@ func (s *E2ETestSuite) SeedPreParams(t *testing.T) { DbPath string `yaml:"db_path"` } require.NoError(t, yaml.Unmarshal(configData, &nodeConfig), "Failed to parse node config") + require.NoError(t, validateBadgerEncryptionKey(nodeConfig.BadgerPassword), "Invalid badger_password in %s", configPath) dbBasePath := nodeConfig.DbPath if dbBasePath == "" { diff --git a/e2e/setup_test_identities.sh b/e2e/setup_test_identities.sh index 00c2dfae..cb594803 100755 --- a/e2e/setup_test_identities.sh +++ b/e2e/setup_test_identities.sh @@ -25,7 +25,11 @@ echo "🚀 Setting up E2E Test Node Identities..." # Generate random password for badger encryption echo "🔐 Generating random password for badger encryption..." -BADGER_PASSWORD=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c 32) +BADGER_PASSWORD=$(openssl rand -hex 16) +if [ ${#BADGER_PASSWORD} -ne 32 ]; then + echo "❌ Generated Badger password must be exactly 32 bytes, got ${#BADGER_PASSWORD}" + exit 1 +fi echo "✅ Generated password: $BADGER_PASSWORD" # Generate chain_code (32-byte hex value, 64 hex characters) From 9ea75a3ada96fca96f1519f0fc61ed72c96a7486 Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Wed, 6 May 2026 14:52:51 +0800 Subject: [PATCH 3/8] - fix `Badger Password` error with e2e tests for both MacOS and Linux --- e2e/setup_test_identities.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/e2e/setup_test_identities.sh b/e2e/setup_test_identities.sh index cb594803..1cbd7861 100755 --- a/e2e/setup_test_identities.sh +++ b/e2e/setup_test_identities.sh @@ -25,7 +25,13 @@ echo "🚀 Setting up E2E Test Node Identities..." # Generate random password for badger encryption echo "🔐 Generating random password for badger encryption..." -BADGER_PASSWORD=$(openssl rand -hex 16) +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + BADGER_PASSWORD=$(openssl rand -hex 16) +else + # Linux and others + BADGER_PASSWORD=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c 32) +fi if [ ${#BADGER_PASSWORD} -ne 32 ]; then echo "❌ Generated Badger password must be exactly 32 bytes, got ${#BADGER_PASSWORD}" exit 1 From 1733feb3834892aff5845677687d21d2270a40de Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Wed, 6 May 2026 18:53:57 +0800 Subject: [PATCH 4/8] - implement storing wallet creation result --- pkg/eventconsumer/event_consumer.go | 4 ++++ pkg/mpc/node.go | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 4b94e2d6..24441506 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -272,6 +272,10 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { return } + if storeErr := ec.node.StoreWalletCreationResult(walletID, payload); storeErr != nil { + logger.Error("Failed to store wallet creation result", storeErr, "walletID", walletID) + } + key := event.KeygenResultSubject(natMsg.Header.Get(event.ClientIDHeader), walletID) if err := ec.genKeyResultQueue.Enqueue( key, diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index e28cf3f0..3910b6dd 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -409,6 +409,18 @@ func (p *Node) CreateReshareSession( } } +const walletCreationResultPrefix = "wallet_creation_result_prefix" + +func (p *Node) StoreWalletCreationResult(walletID string, result []byte) error { + key := fmt.Sprintf("%s:%s", walletCreationResultPrefix, walletID) + return p.kvstore.Put(key, result) +} + +func (p *Node) GetWalletCreationResult(walletID string) ([]byte, error) { + key := fmt.Sprintf("%s:%s", walletCreationResultPrefix, walletID) + return p.kvstore.Get(key) +} + func ComposeReadyKey(nodeID string) string { return fmt.Sprintf("ready/%s", nodeID) } From 29c2151fbb916d15b11230ddef31cd7b475d754b Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Thu, 7 May 2026 15:28:36 +0800 Subject: [PATCH 5/8] - make error handling consistent with others --- pkg/eventconsumer/event_consumer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 24441506..449cf71a 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -274,6 +274,8 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { if storeErr := ec.node.StoreWalletCreationResult(walletID, payload); storeErr != nil { logger.Error("Failed to store wallet creation result", storeErr, "walletID", walletID) + ec.handleKeygenSessionError(walletID, storeErr, "Failed to store wallet creation result", natMsg) + return } key := event.KeygenResultSubject(natMsg.Header.Get(event.ClientIDHeader), walletID) From d0485197635afe0006a09238a558a9beda51a4b7 Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Thu, 7 May 2026 17:29:28 +0800 Subject: [PATCH 6/8] - implement `replay the store wallet creation result` --- e2e/keygen_test.go | 1 + e2e/multi_client_routing_test.go | 2 ++ pkg/eventconsumer/event_consumer.go | 30 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/e2e/keygen_test.go b/e2e/keygen_test.go index e4599161..1d4076da 100644 --- a/e2e/keygen_test.go +++ b/e2e/keygen_test.go @@ -14,6 +14,7 @@ import ( func TestKeyGeneration(t *testing.T) { suite := NewE2ETestSuite(".") + logger.Init("dev", true) // Comprehensive cleanup before starting tests t.Log("Performing pre-test cleanup...") diff --git a/e2e/multi_client_routing_test.go b/e2e/multi_client_routing_test.go index d28d2e7d..83a23f5f 100644 --- a/e2e/multi_client_routing_test.go +++ b/e2e/multi_client_routing_test.go @@ -11,6 +11,7 @@ import ( "github.com/fystack/mpcium/pkg/client" "github.com/fystack/mpcium/pkg/event" + "github.com/fystack/mpcium/pkg/logger" "github.com/fystack/mpcium/pkg/types" "github.com/google/uuid" "github.com/nats-io/nats.go" @@ -36,6 +37,7 @@ type multiClientObserver struct { func TestMultiClientResultRouting(t *testing.T) { suite := NewE2ETestSuite(".") + logger.Init("dev", true) t.Log("Performing pre-test cleanup...") suite.CleanupTestEnvironment(t) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 449cf71a..0ebe2edb 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -158,6 +158,34 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { walletID := msg.WalletID + storedWalletCreationResult, storedWalletCreationResultError := ec.node.GetWalletCreationResult(walletID) + + // Error when retrieving wallet creation result for the wallet ID + if storedWalletCreationResultError != nil { + ec.handleKeygenSessionError(walletID, storedWalletCreationResultError, "Failed to check stored wallet creation result", natMsg) + return + } + + // Replay the wallet creation result if it already exists for the wallet ID + // TODO + // 1. to inspect storedWalletCreationResult + // 2. to move the following duplicate logic (line 341~351) into a func + if storedWalletCreationResult != nil { + logger.Info("storedWalletCreationResult", storedWalletCreationResult) + + key := event.KeygenResultSubject(natMsg.Header.Get(event.ClientIDHeader), walletID) + if err := ec.genKeyResultQueue.Enqueue(key, storedWalletCreationResult, &messaging.EnqueueOptions{ + IdempotententKey: composeKeygenIdempotentKey(walletID, natMsg), + }); err != nil { + logger.Error("Failed to enqueue stored wallet creation result", err, "walletID", walletID) + ec.handleKeygenSessionError(walletID, err, "Failed to enqueue stored wallet creation result", natMsg) + return + } + ec.sendReplyToRemoveMsg(natMsg) + logger.Info("Returned stored wallet creation result for existing wallet", "walletID", walletID) + return + } + // Guard against duplicate keygen sessions for the same walletID. // Under heavy load, the keygen consumer may NAK and JetStream redelivers, // creating a second session on the same NATS topics which causes VSS verify failures. @@ -272,6 +300,8 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { return } + // Store wallet creation result + logger.Info("payload", payload) if storeErr := ec.node.StoreWalletCreationResult(walletID, payload); storeErr != nil { logger.Error("Failed to store wallet creation result", storeErr, "walletID", walletID) ec.handleKeygenSessionError(walletID, storeErr, "Failed to store wallet creation result", natMsg) From 9ca18f561e52ca6738b4bcea4c1eebc79577db50 Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Fri, 8 May 2026 15:29:56 +0800 Subject: [PATCH 7/8] - fix the condition for error handling for `stored wallet creation result` --- pkg/eventconsumer/event_consumer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 0ebe2edb..eebdb575 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/dgraph-io/badger/v4" "github.com/fystack/mpcium/pkg/event" "github.com/fystack/mpcium/pkg/identity" "github.com/fystack/mpcium/pkg/logger" @@ -161,7 +162,7 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { storedWalletCreationResult, storedWalletCreationResultError := ec.node.GetWalletCreationResult(walletID) // Error when retrieving wallet creation result for the wallet ID - if storedWalletCreationResultError != nil { + if storedWalletCreationResultError != nil && !errors.Is(storedWalletCreationResultError, badger.ErrKeyNotFound) { ec.handleKeygenSessionError(walletID, storedWalletCreationResultError, "Failed to check stored wallet creation result", natMsg) return } From e453e3e7501efc19dde1513d8789f929664ceb21 Mon Sep 17 00:00:00 2001 From: Johnny Jiang Date: Sat, 9 May 2026 13:05:34 +0800 Subject: [PATCH 8/8] - update comments - add e2e test for wallet creation result replay --- e2e/keygen_test.go | 23 +++++++++++++++++++---- pkg/eventconsumer/event_consumer.go | 8 ++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/e2e/keygen_test.go b/e2e/keygen_test.go index 1d4076da..387876aa 100644 --- a/e2e/keygen_test.go +++ b/e2e/keygen_test.go @@ -79,10 +79,23 @@ func testKeyGeneration(t *testing.T, suite *E2ETestSuite) { if suite.mpcClient == nil { t.Fatal("MPC client is not initialized. Make sure Setup subtest runs first.") } + + walletID := uuid.New().String() + + // Create a new wallet + createWalletAndVerify(t, suite, walletID) + + // Create another new wallet with the same wallet ID + createWalletAndVerify(t, suite, walletID) + + t.Log("Key generation test completed") +} + +func createWalletAndVerify(t *testing.T, suite *E2ETestSuite, walletID string) { // Generate 1 wallet ID for testing walletIDs := make([]string, 0, 10) for i := 0; i < 1; i++ { - walletIDs = append(walletIDs, uuid.New().String()) + walletIDs = append(walletIDs, walletID) suite.walletIDs = append(suite.walletIDs, walletIDs[i]) } @@ -92,7 +105,11 @@ func testKeyGeneration(t *testing.T, suite *E2ETestSuite) { err := suite.mpcClient.OnWalletCreationResult(func(result event.KeygenResultEvent) { logger.Info("On wallet creation result", "event", result) t.Logf("Received keygen result for wallet %s: %s", result.WalletID, result.ResultType) - suite.keygenResults[result.WalletID] = &result + + // For testing replay properly, don't overwrite existing result for the same wallet ID to preserve the first result + if _, exists := suite.keygenResults[result.WalletID]; !exists { + suite.keygenResults[result.WalletID] = &result + } if result.ResultType == event.ResultTypeError { t.Logf("Keygen failed for wallet %s: %s (%s)", result.WalletID, result.ErrorReason, result.ErrorCode) @@ -165,8 +182,6 @@ checkResults: assert.NotEmpty(t, result.EDDSAPubKey, "EdDSA public key should not be empty for wallet %s", walletID) } } - - t.Log("Key generation test completed") } func verifyKeyConsistency(t *testing.T, suite *E2ETestSuite) { diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index eebdb575..f19d646e 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -159,6 +159,7 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { walletID := msg.WalletID + // Attempt to get previously stored wallet creation result (if any) by the wallet ID storedWalletCreationResult, storedWalletCreationResultError := ec.node.GetWalletCreationResult(walletID) // Error when retrieving wallet creation result for the wallet ID @@ -168,12 +169,8 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { } // Replay the wallet creation result if it already exists for the wallet ID - // TODO - // 1. to inspect storedWalletCreationResult - // 2. to move the following duplicate logic (line 341~351) into a func + // TODO: to move the following duplicate logic (line 308 and line 341) into a func if storedWalletCreationResult != nil { - logger.Info("storedWalletCreationResult", storedWalletCreationResult) - key := event.KeygenResultSubject(natMsg.Header.Get(event.ClientIDHeader), walletID) if err := ec.genKeyResultQueue.Enqueue(key, storedWalletCreationResult, &messaging.EnqueueOptions{ IdempotententKey: composeKeygenIdempotentKey(walletID, natMsg), @@ -302,7 +299,6 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { } // Store wallet creation result - logger.Info("payload", payload) if storeErr := ec.node.StoreWalletCreationResult(walletID, payload); storeErr != nil { logger.Error("Failed to store wallet creation result", storeErr, "walletID", walletID) ec.handleKeygenSessionError(walletID, storeErr, "Failed to store wallet creation result", natMsg)