From f522ad95aefb6706413a479f2e8028237da2a44e Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 7 Jan 2026 17:20:08 +1100 Subject: [PATCH 1/4] feat: core improvements for local development MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task Queue Isolation: - Make task queue name configurable via TASK_QUEUE_NAME env var - Prevents workers from stealing tasks meant for other services Reshare Improvements: - Improved logging with request_party_id and local_party_prefix fields - Fixed party ID generation to use service's own LocalPartyPrefix config Bug Fixes: - Fix policy ID generation: check uuid.Nil instead of empty string - Auth middleware: validate token if provided, even when auth disabled Documentation: - Add worker-config.json example - Add LOCAL_DEPLOYMENT.md cheatsheet 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .run/LOCAL_DEPLOYMENT.md | 149 +++++++++++++++++++++++++++++++++++++ internal/api/middleware.go | 9 +-- internal/api/policy.go | 2 +- plugin/tasks/tasks.go | 12 ++- vault/service.go | 14 ++-- worker-config.json | 34 +++++++++ 6 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 .run/LOCAL_DEPLOYMENT.md create mode 100644 worker-config.json diff --git a/.run/LOCAL_DEPLOYMENT.md b/.run/LOCAL_DEPLOYMENT.md new file mode 100644 index 00000000..efd5c1ff --- /dev/null +++ b/.run/LOCAL_DEPLOYMENT.md @@ -0,0 +1,149 @@ +# Verifier Stack - Local Deployment Cheatsheet + +## Prerequisites +```bash +# Ensure Docker is running +open -a Docker + +# Create shared network (once) +docker network create shared_network + +# Set library path (required for TSS operations) +export DYLD_LIBRARY_PATH=/Users/dev/dev/vultisig/go-wrappers/includes/darwin/:$DYLD_LIBRARY_PATH +``` + +## 1. Start Infrastructure +```bash +cd /Users/dev/dev/vultisig/verifier +docker compose up -d postgres redis minio +``` + +Wait for services to be ready: +```bash +docker exec verifier-postgres-1 pg_isready -U myuser -d vultisig-verifier +``` + +Create MinIO bucket: +```bash +docker exec verifier-minio-1 mc alias set myminio http://localhost:9000 minioadmin minioadmin +docker exec verifier-minio-1 mc mb --ignore-existing myminio/vultisig-verifier +``` + +## 2. Config Files + +### Server Config (`config.json`) +Copy from `verifier.example.json`: +```bash +cp verifier.example.json config.json +``` + +### Worker Config (`worker-config.json`) +**IMPORTANT**: Worker uses `VS_WORKER_CONFIG_NAME` env var, not `VS_CONFIG_NAME` + +Create `worker-config.json` with relay server configured: +```json +{ + "log_format": "text", + "vault_service": { + "relay": { + "server": "https://api.vultisig.com/router" + }, + "local_party_prefix": "verifier", + "encryption_secret": "test123", + "do_setup_msg": false + }, + "redis": { + "host": "localhost", + "port": "6379" + }, + "block_storage": { + "host": "http://localhost:9000", + "region": "us-east-1", + "access_key": "minioadmin", + "secret": "minioadmin", + "bucket": "vultisig-verifier" + }, + "database": { + "dsn": "postgres://myuser:mypassword@localhost:5432/vultisig-verifier?sslmode=disable" + }, + "plugin": {}, + "fees": { + "usdc_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + "metrics": { + "enabled": true, + "host": "0.0.0.0", + "port": 8089 + } +} +``` + +## 3. Run Services + +### Verifier Server (port 8080) +```bash +VS_CONFIG_NAME=config go run cmd/verifier/main.go +``` + +### Verifier Worker +```bash +VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go +``` + +## 4. Update Plugin Endpoints in Database + +After starting, update plugin endpoints to point to local services: +```bash +docker exec verifier-postgres-1 psql -U myuser -d vultisig-verifier -c \ + "UPDATE plugins SET server_endpoint = 'http://localhost:8085' WHERE id = 'vultisig-fees-feee';" + +docker exec verifier-postgres-1 psql -U myuser -d vultisig-verifier -c \ + "UPDATE plugins SET server_endpoint = 'http://localhost:8082' WHERE id = 'vultisig-dca-0000';" + +docker exec verifier-postgres-1 psql -U myuser -d vultisig-verifier -c \ + "UPDATE plugins SET server_endpoint = 'http://localhost:8083' WHERE id = 'vultisig-recurring-sends-0000';" +``` + +## Quick Reference + +| Service | Port | Config Env Var | +|---------|------|----------------| +| Verifier Server | 8080 | `VS_CONFIG_NAME` | +| Verifier Worker | - | `VS_WORKER_CONFIG_NAME` | +| PostgreSQL | 5432 | - | +| Redis | 6379 | - | +| MinIO | 9000 (API), 9090 (Console) | - | + +## 5. Configure Extension for Local Development + +The browser extension defaults to production verifier. You must change it to use localhost. + +1. In the extension, find the version text at bottom (e.g., "VULTISIG EXTENSION V1.x.x") +2. **Click it 3 times** to open Developer Options modal +3. Change **Plugin Server URL** from `https://verifier.vultisig.com` to `http://localhost:8080` +4. Click Save + +## Troubleshooting + +### "unsupported protocol scheme" error +Worker config missing `vault_service.relay.server`. Ensure `worker-config.json` has the relay URL. + +### Plugin installation stuck at "connecting to app store" +Extension is talking to production verifier instead of local. Configure Developer Options: +1. Click version text 3 times to open Developer Options +2. Set Plugin Server URL to `http://localhost:8080` + +### Plugin installation stuck at "connecting to server" +The TSS session is registered but waiting for the other party. Check: +1. Extension/app is using same relay server (`https://api.vultisig.com/router`) +2. Session ID matches between worker logs and extension +3. Extension Developer Options pointing to `http://localhost:8080` + +### Metrics port conflict +Non-critical - worker/server share default metrics port. Update `metrics.port` in configs to use different ports. + +## Stop Services +```bash +docker compose down +pkill -f "go run cmd" +``` diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 22f09b9b..1c758817 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -37,13 +37,12 @@ func (s *Server) AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { // VaultAuthMiddleware verifies JWT tokens and ensures users can only access their own vaults. func (s *Server) VaultAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - if s.cfg.Auth.Enabled != nil && !*s.cfg.Auth.Enabled { - s.logger.Info("Auth is disabled, skipping token validation") - return next(c) - } - authHeader := c.Request().Header.Get(echo.HeaderAuthorization) if authHeader == "" { + if s.cfg.Auth.Enabled != nil && !*s.cfg.Auth.Enabled { + s.logger.Info("Auth is disabled and no token provided") + return next(c) + } return c.JSON(http.StatusUnauthorized, NewErrorResponseWithMessage(msgMissingAuthHeader)) } diff --git a/internal/api/policy.go b/internal/api/policy.go index df20d99b..afba7068 100644 --- a/internal/api/policy.go +++ b/internal/api/policy.go @@ -68,7 +68,7 @@ func (s *Server) CreatePluginPolicy(c echo.Context) error { s.logger.WithError(err).Error("Failed to parse request") return c.JSON(http.StatusBadRequest, NewErrorResponseWithMessage(msgRequestParseFailed)) } - if policy.ID.String() == "" { + if policy.ID == uuid.Nil { policy.ID = uuid.New() } publicKey, ok := c.Get("vault_public_key").(string) diff --git a/plugin/tasks/tasks.go b/plugin/tasks/tasks.go index 730c5359..7c23308f 100644 --- a/plugin/tasks/tasks.go +++ b/plugin/tasks/tasks.go @@ -2,11 +2,21 @@ package tasks import ( "fmt" + "os" "github.com/hibiken/asynq" ) -const QUEUE_NAME = "default_queue" +const defaultQueueName = "default_queue" + +var QUEUE_NAME = getQueueName() + +func getQueueName() string { + if name := os.Getenv("TASK_QUEUE_NAME"); name != "" { + return name + } + return defaultQueueName +} const ( TypeRecurringFeeRecord = "fee:recurringRecord" diff --git a/vault/service.go b/vault/service.go index 76c6c6ea..3a6f43a0 100644 --- a/vault/service.go +++ b/vault/service.go @@ -209,10 +209,11 @@ func (s *ManagementService) HandleReshareDKLS(ctx context.Context, t *asynq.Task } s.logger.WithFields(logrus.Fields{ - "name": req.Name, - "session": req.SessionID, - "local_party_id": req.LocalPartyId, - "email": req.Email, + "name": req.Name, + "session": req.SessionID, + "request_party_id": req.LocalPartyId, + "local_party_prefix": s.cfg.LocalPartyPrefix, + "email": req.Email, }).Info("reshare request") if err := req.IsValid(); err != nil { return fmt.Errorf("invalid reshare request: %s: %w", err, asynq.SkipRetry) @@ -223,12 +224,15 @@ func (s *ManagementService) HandleReshareDKLS(ctx context.Context, t *asynq.Task vaultFileName := vcommon.GetVaultBackupFilename(req.PublicKey, req.PluginID) vaultContent, err := s.vaultStorage.GetVault(vaultFileName) if err != nil || vaultContent == nil { + // Generate local party ID using the configured prefix, NOT the one from the request + // Each plugin/service should have its own party ID based on its LocalPartyPrefix config + localPartyID := s.cfg.LocalPartyPrefix + "-" + req.SessionID[:8] vault = &vaultType.Vault{ Name: req.Name, PublicKeyEcdsa: "", PublicKeyEddsa: "", HexChainCode: req.HexChainCode, - LocalPartyId: vcommon.GenerateLocalPartyId(s.cfg.LocalPartyPrefix), + LocalPartyId: localPartyID, Signers: req.OldParties, LibType: keygenType.LibType_LIB_TYPE_DKLS, } diff --git a/worker-config.json b/worker-config.json new file mode 100644 index 00000000..3c9ed772 --- /dev/null +++ b/worker-config.json @@ -0,0 +1,34 @@ +{ + "log_format": "text", + "vault_service": { + "relay": { + "server": "https://api.vultisig.com/router" + }, + "local_party_prefix": "verifier", + "encryption_secret": "test123", + "do_setup_msg": false + }, + "redis": { + "host": "localhost", + "port": "6379" + }, + "block_storage": { + "host": "http://localhost:9000", + "region": "us-east-1", + "access_key": "minioadmin", + "secret": "minioadmin", + "bucket": "vultisig-verifier" + }, + "database": { + "dsn": "postgres://myuser:mypassword@localhost:5432/vultisig-verifier?sslmode=disable" + }, + "plugin": {}, + "fees": { + "usdc_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + "metrics": { + "enabled": true, + "host": "0.0.0.0", + "port": 8089 + } +} From dd7618df05e6dd23610385f02c238ee59009a222 Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 7 Jan 2026 17:33:02 +1100 Subject: [PATCH 2/4] docs: address CodeRabbit review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clarify DYLD_LIBRARY_PATH is macOS-only - Add TASK_QUEUE_NAME documentation for multi-worker setups 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .run/LOCAL_DEPLOYMENT.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.run/LOCAL_DEPLOYMENT.md b/.run/LOCAL_DEPLOYMENT.md index efd5c1ff..854e1ad2 100644 --- a/.run/LOCAL_DEPLOYMENT.md +++ b/.run/LOCAL_DEPLOYMENT.md @@ -8,7 +8,8 @@ open -a Docker # Create shared network (once) docker network create shared_network -# Set library path (required for TSS operations) +# Set library path (required for TSS operations - macOS only) +# Linux/Windows users: skip this step, the library is statically linked export DYLD_LIBRARY_PATH=/Users/dev/dev/vultisig/go-wrappers/includes/darwin/:$DYLD_LIBRARY_PATH ``` @@ -90,6 +91,16 @@ VS_CONFIG_NAME=config go run cmd/verifier/main.go VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go ``` +**Task Queue Isolation**: When running multiple workers (e.g., verifier + DCA plugin), use `TASK_QUEUE_NAME` to prevent workers from stealing each other's tasks: +```bash +# Verifier worker (default queue) +VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go + +# DCA plugin worker (separate queue) +TASK_QUEUE_NAME=dca_plugin_queue VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go +``` +For single-worker setups, the default queue (`default_queue`) is sufficient. + ## 4. Update Plugin Endpoints in Database After starting, update plugin endpoints to point to local services: From c5633191d80df7c962ecf5428b234351aec0119b Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 7 Jan 2026 17:33:24 +1100 Subject: [PATCH 3/4] chore: remove redundant LOCAL_DEPLOYMENT.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Info already exists in devenv/README.md and CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .run/LOCAL_DEPLOYMENT.md | 160 --------------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 .run/LOCAL_DEPLOYMENT.md diff --git a/.run/LOCAL_DEPLOYMENT.md b/.run/LOCAL_DEPLOYMENT.md deleted file mode 100644 index 854e1ad2..00000000 --- a/.run/LOCAL_DEPLOYMENT.md +++ /dev/null @@ -1,160 +0,0 @@ -# Verifier Stack - Local Deployment Cheatsheet - -## Prerequisites -```bash -# Ensure Docker is running -open -a Docker - -# Create shared network (once) -docker network create shared_network - -# Set library path (required for TSS operations - macOS only) -# Linux/Windows users: skip this step, the library is statically linked -export DYLD_LIBRARY_PATH=/Users/dev/dev/vultisig/go-wrappers/includes/darwin/:$DYLD_LIBRARY_PATH -``` - -## 1. Start Infrastructure -```bash -cd /Users/dev/dev/vultisig/verifier -docker compose up -d postgres redis minio -``` - -Wait for services to be ready: -```bash -docker exec verifier-postgres-1 pg_isready -U myuser -d vultisig-verifier -``` - -Create MinIO bucket: -```bash -docker exec verifier-minio-1 mc alias set myminio http://localhost:9000 minioadmin minioadmin -docker exec verifier-minio-1 mc mb --ignore-existing myminio/vultisig-verifier -``` - -## 2. Config Files - -### Server Config (`config.json`) -Copy from `verifier.example.json`: -```bash -cp verifier.example.json config.json -``` - -### Worker Config (`worker-config.json`) -**IMPORTANT**: Worker uses `VS_WORKER_CONFIG_NAME` env var, not `VS_CONFIG_NAME` - -Create `worker-config.json` with relay server configured: -```json -{ - "log_format": "text", - "vault_service": { - "relay": { - "server": "https://api.vultisig.com/router" - }, - "local_party_prefix": "verifier", - "encryption_secret": "test123", - "do_setup_msg": false - }, - "redis": { - "host": "localhost", - "port": "6379" - }, - "block_storage": { - "host": "http://localhost:9000", - "region": "us-east-1", - "access_key": "minioadmin", - "secret": "minioadmin", - "bucket": "vultisig-verifier" - }, - "database": { - "dsn": "postgres://myuser:mypassword@localhost:5432/vultisig-verifier?sslmode=disable" - }, - "plugin": {}, - "fees": { - "usdc_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" - }, - "metrics": { - "enabled": true, - "host": "0.0.0.0", - "port": 8089 - } -} -``` - -## 3. Run Services - -### Verifier Server (port 8080) -```bash -VS_CONFIG_NAME=config go run cmd/verifier/main.go -``` - -### Verifier Worker -```bash -VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go -``` - -**Task Queue Isolation**: When running multiple workers (e.g., verifier + DCA plugin), use `TASK_QUEUE_NAME` to prevent workers from stealing each other's tasks: -```bash -# Verifier worker (default queue) -VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go - -# DCA plugin worker (separate queue) -TASK_QUEUE_NAME=dca_plugin_queue VS_WORKER_CONFIG_NAME=worker-config go run cmd/worker/main.go -``` -For single-worker setups, the default queue (`default_queue`) is sufficient. - -## 4. Update Plugin Endpoints in Database - -After starting, update plugin endpoints to point to local services: -```bash -docker exec verifier-postgres-1 psql -U myuser -d vultisig-verifier -c \ - "UPDATE plugins SET server_endpoint = 'http://localhost:8085' WHERE id = 'vultisig-fees-feee';" - -docker exec verifier-postgres-1 psql -U myuser -d vultisig-verifier -c \ - "UPDATE plugins SET server_endpoint = 'http://localhost:8082' WHERE id = 'vultisig-dca-0000';" - -docker exec verifier-postgres-1 psql -U myuser -d vultisig-verifier -c \ - "UPDATE plugins SET server_endpoint = 'http://localhost:8083' WHERE id = 'vultisig-recurring-sends-0000';" -``` - -## Quick Reference - -| Service | Port | Config Env Var | -|---------|------|----------------| -| Verifier Server | 8080 | `VS_CONFIG_NAME` | -| Verifier Worker | - | `VS_WORKER_CONFIG_NAME` | -| PostgreSQL | 5432 | - | -| Redis | 6379 | - | -| MinIO | 9000 (API), 9090 (Console) | - | - -## 5. Configure Extension for Local Development - -The browser extension defaults to production verifier. You must change it to use localhost. - -1. In the extension, find the version text at bottom (e.g., "VULTISIG EXTENSION V1.x.x") -2. **Click it 3 times** to open Developer Options modal -3. Change **Plugin Server URL** from `https://verifier.vultisig.com` to `http://localhost:8080` -4. Click Save - -## Troubleshooting - -### "unsupported protocol scheme" error -Worker config missing `vault_service.relay.server`. Ensure `worker-config.json` has the relay URL. - -### Plugin installation stuck at "connecting to app store" -Extension is talking to production verifier instead of local. Configure Developer Options: -1. Click version text 3 times to open Developer Options -2. Set Plugin Server URL to `http://localhost:8080` - -### Plugin installation stuck at "connecting to server" -The TSS session is registered but waiting for the other party. Check: -1. Extension/app is using same relay server (`https://api.vultisig.com/router`) -2. Session ID matches between worker logs and extension -3. Extension Developer Options pointing to `http://localhost:8080` - -### Metrics port conflict -Non-critical - worker/server share default metrics port. Update `metrics.port` in configs to use different ports. - -## Stop Services -```bash -docker compose down -pkill -f "go run cmd" -``` From f5a666311c00794f85aebf12993699ba90788e40 Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 7 Jan 2026 17:40:06 +1100 Subject: [PATCH 4/4] fix: gofmt formatting --- vault/service.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vault/service.go b/vault/service.go index 3a6f43a0..b7075ab2 100644 --- a/vault/service.go +++ b/vault/service.go @@ -209,11 +209,11 @@ func (s *ManagementService) HandleReshareDKLS(ctx context.Context, t *asynq.Task } s.logger.WithFields(logrus.Fields{ - "name": req.Name, - "session": req.SessionID, - "request_party_id": req.LocalPartyId, + "name": req.Name, + "session": req.SessionID, + "request_party_id": req.LocalPartyId, "local_party_prefix": s.cfg.LocalPartyPrefix, - "email": req.Email, + "email": req.Email, }).Info("reshare request") if err := req.IsValid(); err != nil { return fmt.Errorf("invalid reshare request: %s: %w", err, asynq.SkipRetry)