Solana addresses were correct and receive-only: ConsensusKeyInfoStore.Save was the only RegisterKey caller and hardcoded "secp256k1", and TriggerSign read the curve from that field, so no product entry point could reach Ed25519 signing. The hardcode is not fixed, it is UNREPRESENTABLE. transport.KeyInfo.KeyType is deleted — the record carries one public key per curve and a PublicKeyFor(curve) — and RegisterKey lost its keyType parameter, so there is no longer a place to state a curve wrongly. A scalar KeyType was the wrong shape anyway: one keygen ceremony produces BOTH a secp256k1 and an Ed25519 key, so "what curve is this wallet" has no single answer. The request carries the network instead, and ONE table maps it to a curve, with NO default arm — KeyTypeForNetwork returns ErrUnknownNetwork rather than guessing. That is the point: the old default returned secp256k1 for anything unrecognized, which is how a Solana request would have been signed with the wrong key. /sign's 32-byte clamp becomes the SCHEME's rule rather than the endpoint's: secp256k1 signs a digest and requires exactly 32 bytes, Ed25519 signs the message at any length. /sign no longer accepts key_type at all — two ways to say the curve is the misuse being removed — and Network is bound into the idempotency fingerprint, so the same bytes for SOL and ETH conflict instead of sharing one cached signature from a different key. ⚠ The trap this nearly shipped with: every pre-existing KeyInfo record has EMPTY public keys, because the old Save wrote "", "". A naive "does this wallet have this curve" gate would have refused every existing wallet on every curve. The check is gated on RecordsCurves() — a record naming no curve denies none, and the missing share stays the thing that fails closed. Verified end to end, not by mirroring: a Solana request through ConsensusMPCBackend.TriggerSign with a real 2-of-3 FROST ring produces a signature crypto/ed25519.Verify accepts, over a 63-byte message so any reintroduced clamp or pre-hash fails it; ETH/EVM/BTC/LUX still route to secp256k1 and must RECOVER to the wallet's key. Four mutations tried, all caught. Behavioral risk worth naming: an unrecognized chain string now fails its transaction closed rather than signing with a default. That is correct, and the real fix is validating chain at POST /v1/transactions.
E2E Testing for MPCium
This directory contains end-to-end integration tests for the MPCium multi-party computation system.
Overview
The E2E tests verify that the complete MPC system works correctly by:
- Infrastructure Setup: Using testcontainers to spin up isolated NATS and Consul instances
- Node Setup: Creating 3 test nodes with separate identities and configurations
- Key Generation: Testing concurrent key generation for multiple wallets
- Consistency Verification: Ensuring all generated keys are properly stored across all nodes
- Cleanup: Removing all test artifacts and containers
Prerequisites
Before running the tests, ensure you have:
- Docker installed and running
- Go 1.23+ installed
- lux-mpc and lux-mpc-cli binaries built (run
makein the root directory)
Running Tests
Quick Start
# Run all E2E tests
make test
# Clean up test artifacts
make clean
Manual Steps
-
Build the binaries (from root directory):
make -
Run the E2E tests:
cd e2e make test
Test Structure
Files
keygen_test.go- Main test file with the E2E test suitedocker-compose.test.yaml- Test infrastructure configurationconfig.test.yaml- Test node configuration templatesetup_test_identities.sh- Script to set up test node identitiesMakefile- Build and test automation
Test Flow
-
Setup Infrastructure
- Starts NATS (port 4223) and Consul (port 8501) containers
- Creates service clients for test coordination
-
Setup Test Nodes
- Creates 3 test nodes (
test_node0,test_node1,test_node2) - Generates unique identities for each node
- Configures separate database paths (
./test_db/) - Registers peers in Consul
- Creates 3 test nodes (
-
Start MPC Nodes
- Launches 3 mpc processes in parallel
- Each node uses its own configuration and identity
-
Test Key Generation
- Generates 3 random wallet IDs
- Triggers key generation for all wallets simultaneously
- Waits for completion (2 minute timeout)
-
Verify Consistency
- Stops all nodes safely
- Opens each node's database in read-only mode
- Verifies both ECDSA and EdDSA keys exist for each wallet
- Ensures all nodes have identical key data
-
Cleanup
- Stops all processes
- Removes Docker containers
- Deletes test databases and temporary files
Configuration
Test Ports
The tests use different ports to avoid conflicts with running services:
- NATS: 4223 (vs 4222 for main)
- Consul: 8501 (vs 8500 for main)
Database Path
Test nodes use a separate database path: ./test_db/ instead of ./db/
Test Credentials
- Badger Password:
test_password_123 - Node Names:
test_node0,test_node1,test_node2
Troubleshooting
Common Issues
-
Binary not found
❌ mpc binary not found. Please run 'make' in the root directory first.Solution: Run
makein the root directory to build the binaries. -
Port conflicts
Error: port 4223 already in useSolution: Run
make cleanto stop any existing test containers. -
Permission errors
Error: cannot create test_db directorySolution: Ensure you have write permissions in the e2e directory.
Debugging
To debug test failures:
-
Check container logs:
docker logs nats-server-test docker logs consul-test -
Run with verbose output:
go test -v -timeout=10m ./... -
Keep test artifacts (comment out cleanup in the test):
# Inspect test databases ls -la test_db/ # Check test node configurations cat test_node0/config.yaml
Expected Output
A successful test run should show:
🚀 Setting up test infrastructure...
🐳 Starting docker-compose stack...
⏳ Waiting for services to be ready...
🔌 Setting up service clients...
✅ Consul client connected
✅ NATS client connected
🔧 Setting up test nodes...
✅ Test nodes setup complete
📋 Registering peers in Consul...
✅ Registered peer test_node0 with ID xxx
✅ Registered peer test_node1 with ID xxx
✅ Registered peer test_node2 with ID xxx
🚀 Starting MPC nodes...
✅ Started node test_node0 (PID: xxx)
✅ Started node test_node1 (PID: xxx)
✅ Started node test_node2 (PID: xxx)
🔑 Testing key generation...
📝 Generated wallet IDs: [xxx, xxx, xxx]
🔐 Triggering key generation for wallet xxx
⏳ Waiting for key generation to complete...
✅ Key generation test completed
🔍 Verifying key consistency across nodes...
🛑 Stopping MPC nodes...
🔍 Checking wallet xxx
✅ Found ECDSA key for wallet xxx in node test_node0 (xxx bytes)
✅ Found ECDSA key for wallet xxx in node test_node1 (xxx bytes)
✅ Found ECDSA key for wallet xxx in node test_node2 (xxx bytes)
✅ Found EdDSA key for wallet xxx in node test_node0 (xxx bytes)
✅ Found EdDSA key for wallet xxx in node test_node1 (xxx bytes)
✅ Found EdDSA key for wallet xxx in node test_node2 (xxx bytes)
✅ Key consistency verification completed
🧹 Cleaning up test environment...
✅ Cleanup completed
Integration with CI/CD
To integrate with CI/CD pipelines:
# Example GitHub Actions step
- name: Run E2E Tests
run: |
make
cd e2e
make test
The tests are designed to be:
- Isolated: No dependencies on external services
- Deterministic: Consistent results across runs
- Self-contained: All setup and cleanup handled automatically
- Fast: Complete in under 10 minutes
Test Cleanup and Process Management
Problem: Test Process Interference
E2E tests can sometimes leave behind running processes that interfere with subsequent test runs. This can cause signature verification errors and other unexpected failures.
Solution: Comprehensive Cleanup
The test suite now includes automatic cleanup mechanisms to prevent process interference:
1. Automatic Cleanup in Tests
- Pre-test cleanup: Every test run starts with
CleanupTestEnvironment()which:- Kills any existing MPC processes
- Stops Docker containers
- Removes test artifacts
- Post-test cleanup: Tests use
deferto ensure cleanup happens even if tests fail
2. Manual Cleanup Options
If you need to manually clean up the test environment:
# Option 1: Use the cleanup script directly
cd e2e
./cleanup_test_env.sh
# Option 2: Use the Makefile target
make cleanup-test-env
# Option 3: Manual cleanup commands
cd e2e
# Kill MPC processes
pgrep -f "mpc" | xargs kill -TERM
# Stop Docker containers
docker compose -f docker-compose.test.yaml down -v --remove-orphans
# Remove test artifacts
rm -rf test_node* *.log
3. Troubleshooting Process Issues
If you encounter signature verification errors or other mysterious test failures:
-
Check for running processes:
ps aux | grep mpc -
Kill any found processes:
pkill -f mpc -
Clean up completely:
make cleanup-test-env -
Run tests again:
go test -v -run TestKeyGeneration
Best Practices
- Always clean up before running tests - The test suite does this automatically
- Use the cleanup script if you interrupt tests manually (Ctrl+C)
- Check for leftover processes if you see unexpected errors
- Don't run multiple test instances simultaneously in the same directory
Test Structure
base_test.go- Core test infrastructure and cleanup functionskeygen_test.go- Key generation testscleanup_test_env.sh- Standalone cleanup scriptdocker-compose.test.yaml- Test infrastructure (NATS, Consul)
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| "Failed to verify initiator message" | Multiple MPC instances running | Run cleanup script |
| "Port already in use" | Docker containers still running | docker compose down -v |
| "Database locked" | Previous test didn't clean up | Remove test_node* directories |
| Test hangs during setup | Leftover processes interfering | Kill all mpc processes |