diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1c7f611..0000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Use the official Golang image as the base image -FROM golang:latest AS builder - -# Set the Current Working Directory inside the container -WORKDIR /app - -# Copy go.mod and go.sum files -COPY go.mod go.sum ./ - -# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed -RUN go mod download - -# Copy the source from the current directory to the Working Directory inside the container -COPY . . - -# Build the Go app -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main cmd/vultisigner/main.go - -# Start a new stage from scratch -FROM alpine:latest - -# Set the Current Working Directory inside the container -WORKDIR /root/ - -# Copy the Pre-built binary file from the previous stage -COPY --from=builder /app/main . - -# Copy the configuration file -# COPY config.yaml . - -# Expose port 8080 to the outside world -EXPOSE 8080 - -ENV ENV=production - -# Command to run the executable -CMD ["./main"] diff --git a/Dockerfile.postgres b/Dockerfile.postgres deleted file mode 100644 index 33b8d57..0000000 --- a/Dockerfile.postgres +++ /dev/null @@ -1,4 +0,0 @@ -# Use a specific version if you prefer, e.g. postgres:15 -FROM postgres:17 - -COPY init-scripts/ /docker-entrypoint-initdb.d/ diff --git a/cmd/treasury/server/main.go b/cmd/treasury/server/main.go new file mode 100644 index 0000000..4607ea5 --- /dev/null +++ b/cmd/treasury/server/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "context" + "fmt" + "net" + + "github.com/DataDog/datadog-go/statsd" + "github.com/hibiken/asynq" + "github.com/sirupsen/logrus" + "github.com/vultisig/plugin/internal/scheduler" + "github.com/vultisig/plugin/internal/verifierapi" + "github.com/vultisig/verifier/tx_indexer" + tx_indexer_storage "github.com/vultisig/verifier/tx_indexer/pkg/storage" + "github.com/vultisig/verifier/vault" + + "github.com/vultisig/plugin/api" + feeconfig "github.com/vultisig/plugin/cmd/fees/config" + "github.com/vultisig/plugin/plugin/treasury" + "github.com/vultisig/plugin/storage" + "github.com/vultisig/plugin/storage/postgres" +) + +func main() { + ctx := context.Background() + + cfg, err := feeconfig.GetConfigure() + if err != nil { + panic(err) + } + + logger := logrus.New() + sdClient, err := statsd.New(net.JoinHostPort(cfg.Datadog.Host, cfg.Datadog.Port)) + if err != nil { + panic(err) + } + redisStorage, err := storage.NewRedisStorage(cfg.Redis) + if err != nil { + panic(err) + } + redisOptions := asynq.RedisClientOpt{ + Addr: net.JoinHostPort(cfg.Redis.Host, cfg.Redis.Port), + Username: cfg.Redis.User, + Password: cfg.Redis.Password, + DB: cfg.Redis.DB, + } + + client := asynq.NewClient(redisOptions) + defer func() { + if err := client.Close(); err != nil { + fmt.Println("fail to close asynq client,", err) + } + }() + + inspector := asynq.NewInspector(redisOptions) + + vaultStorage, err := vault.NewBlockStorageImp(cfg.BlockStorage) + if err != nil { + panic(err) + } + + txIndexerStore, err := tx_indexer_storage.NewPostgresTxIndexStore(ctx, cfg.Database.DSN) + if err != nil { + panic(fmt.Errorf("tx_indexer_storage.NewPostgresTxIndexStore: %w", err)) + } + + _ = tx_indexer.NewService( + logger, + txIndexerStore, + tx_indexer.Chains(), + ) + + db, err := postgres.NewPostgresBackend(cfg.Database.DSN, nil) + if err != nil { + logger.Fatalf("Failed to connect to database: %v", err) + } + + treasuryPluginConfig, err := treasury.NewTreasuryConfig( + treasury.WithChainIdRaw(1), + treasury.WithEthProviderRaw("https://eth.public-rpc.com"), + treasury.WithEncryptionSecret(cfg.Server.EncryptionSecret)) + + if err != nil { + logger.Fatalf("failed to create treasury config,err: %s", err) + } + + verifierApi := verifierapi.NewVerifierApi( + cfg.Verifier.URL, + cfg.Verifier.Token, + logger.WithField("pkg", "verifierapi.VerifierApi").Logger, + ) + + treasuryPlugin, err := treasury.NewTreasuryPlugin( + treasuryPluginConfig, + vaultStorage, + nil, + db, + logger, + verifierApi, + ) + if err != nil { + logger.Fatalf("failed to create fee plugin,err: %s", err) + } + + server := api.NewServer( + cfg.Server, + db, + redisStorage, + vaultStorage, + client, + inspector, + sdClient, + treasuryPlugin, + scheduler.NewNilService(), + ) + if err := server.StartServer(); err != nil { + panic(err) + } +} diff --git a/cmd/treasury/worker/main.go b/cmd/treasury/worker/main.go new file mode 100644 index 0000000..6b09a65 --- /dev/null +++ b/cmd/treasury/worker/main.go @@ -0,0 +1,195 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/DataDog/datadog-go/statsd" + "github.com/hibiken/asynq" + "github.com/robfig/cron/v3" + "github.com/sirupsen/logrus" + "github.com/vultisig/verifier/plugin/keysign" + "github.com/vultisig/verifier/plugin/tasks" + "github.com/vultisig/verifier/plugin/tx_indexer" + "github.com/vultisig/verifier/plugin/tx_indexer/pkg/storage" + "github.com/vultisig/verifier/vault" + "github.com/vultisig/vultiserver/relay" + + feeconfig "github.com/vultisig/plugin/cmd/fees/config" + "github.com/vultisig/plugin/internal/verifierapi" + "github.com/vultisig/plugin/plugin/treasury" + "github.com/vultisig/plugin/storage/postgres" +) + +func main() { + ctx := context.Background() + + cfg, err := feeconfig.GetConfigure() + if err != nil { + panic(err) + } + + sdClient, err := statsd.New(cfg.Datadog.Host + ":" + cfg.Datadog.Port) + if err != nil { + panic(err) + } + + vaultStorage, err := vault.NewBlockStorageImp(cfg.BlockStorage) + if err != nil { + panic(fmt.Sprintf("failed to initialize vault storage: %v", err)) + } + + redisOptions := asynq.RedisClientOpt{ + Addr: cfg.Redis.Host + ":" + cfg.Redis.Port, + Username: cfg.Redis.User, + Password: cfg.Redis.Password, + DB: cfg.Redis.DB, + } + logger := logrus.StandardLogger() + logger.SetFormatter(&logrus.TextFormatter{ + FullTimestamp: true, + TimestampFormat: time.RFC3339, + }) + + asynqClient := asynq.NewClient(redisOptions) + + srv := asynq.NewServer( + redisOptions, + asynq.Config{ + Logger: logger, + Concurrency: 10, + Queues: map[string]int{ + tasks.QUEUE_NAME: 10, + }, + }, + ) + + postgressDB, err := postgres.NewPostgresBackend(cfg.Database.DSN, nil) + if err != nil { + panic(fmt.Errorf("failed to create postgres backend: %w", err)) + } + + txIndexerStore, err := storage.NewPostgresTxIndexStore(ctx, cfg.Database.DSN) + if err != nil { + panic(fmt.Errorf("storage.NewPostgresTxIndexStore: %w", err)) + } + + chains, err := tx_indexer.Chains() + if err != nil { + panic(fmt.Errorf("failed to initialize tx indexer chains: %w", err)) + } + + txIndexerService := tx_indexer.NewService( + logger, + txIndexerStore, + chains, + ) + + vaultService, err := vault.NewManagementService( + cfg.VaultServiceConfig, + asynqClient, + sdClient, + vaultStorage, + txIndexerService, + ) + + if err != nil { + panic(fmt.Errorf("failed to create vault service: %w", err)) + } + + treasuryPluginConfig, err := treasury.NewTreasuryConfig( + treasury.WithChainIdRaw(1), + treasury.WithEthProviderRaw("https://eth.public-rpc.com"), + treasury.WithEncryptionSecret(cfg.VaultServiceConfig.EncryptionSecret), + ) + + if err != nil { + logger.Fatalf("failed to create fees config,err: %s", err) + } + + signer := keysign.NewSigner( + logger.WithField("pkg", "keysign.Signer").Logger, + relay.NewRelayClient(cfg.VaultServiceConfig.Relay.Server), + []keysign.Emitter{ + keysign.NewVerifierEmitter(cfg.Verifier.URL, cfg.Verifier.Token), + keysign.NewPluginEmitter(asynqClient, tasks.TypeKeySignDKLS, tasks.QUEUE_NAME), + }, + []string{ + cfg.Verifier.PartyPrefix, + cfg.VaultServiceConfig.LocalPartyPrefix, + }, + ) + + verifierApi := verifierapi.NewVerifierApi( + cfg.Verifier.URL, + cfg.Verifier.Token, + logger.WithField("pkg", "verifierapi.VerifierApi").Logger, + ) + + treasuryPlugin, err := treasury.NewTreasuryPlugin( + treasuryPluginConfig, + vaultStorage, + signer, + postgressDB, + logger, + verifierApi, + ) + if err != nil { + logger.Fatalf("failed to create fee plugin,err: %s", err) + } + + mux := asynq.NewServeMux() + // mux.HandleFunc(tasks.TypePluginTransaction, vaultService.HandlePluginTransaction) + + //Core functions, every plugin should have these functions + mux.HandleFunc(tasks.TypeKeySignDKLS, vaultService.HandleKeySignDKLS) + mux.HandleFunc(tasks.TypeReshareDKLS, vaultService.HandleReshareDKLS) + + //Plugin specific functions. + mux.HandleFunc(treasury.TypeTreasuryLoad, treasuryPlugin.LoadTreasuryPayments) + mux.HandleFunc(treasury.TypeTreasuryTransact, treasuryPlugin.TransactTreasuryPayments) + mux.HandleFunc(treasury.TypeTreasuryPostTx, treasuryPlugin.PostTreasuryPayments) + + loadTreasuryPaymentsCron := cron.New() + loadTreasuryPaymentsCron.AddFunc(treasuryPluginConfig.Jobs.Load.Cronexpr, func() { + payload := make([]byte, 0) + asynqClient.Enqueue( + asynq.NewTask(treasury.TypeTreasuryLoad, payload), + asynq.MaxRetry(0), + asynq.Timeout(2*time.Minute), + asynq.Retention(5*time.Minute), + asynq.Queue(tasks.QUEUE_NAME)) + }) + loadTreasuryPaymentsCron.Start() + + transactTreasuryPaymentsCron := cron.New() + transactTreasuryPaymentsCron.AddFunc(treasuryPluginConfig.Jobs.Transact.Cronexpr, func() { + payload := make([]byte, 0) + asynqClient.Enqueue( + asynq.NewTask(treasury.TypeTreasuryTransact, payload), + asynq.MaxRetry(0), + asynq.Timeout(2*time.Minute), + asynq.Retention(5*time.Minute), + asynq.Queue(tasks.QUEUE_NAME)) + }) + transactTreasuryPaymentsCron.Start() + + postTreasuryPaymentsCron := cron.New() + postTreasuryPaymentsCron.AddFunc(treasuryPluginConfig.Jobs.Post.Cronexpr, func() { + payload := make([]byte, 0) + asynqClient.Enqueue( + asynq.NewTask(treasury.TypeTreasuryPostTx, payload), + asynq.MaxRetry(0), + asynq.Timeout(2*time.Minute), + asynq.Retention(5*time.Minute), + asynq.Queue(tasks.QUEUE_NAME)) + }) + postTreasuryPaymentsCron.Start() + + logger.Info("Starting asynq listener") + + if err := srv.Run(mux); err != nil { + panic(fmt.Errorf("could not run server: %w", err)) + } +} diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 5ba7d8e..3424051 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -2,7 +2,7 @@ services: fee-worker: build: context: . - dockerfile: Dockerfile.Fee.worker.dev + dockerfile: dockerfiles/Dockerfile.Fee.worker.dev volumes: - ./cmd:/app/cmd - ./plugin:/app/plugin @@ -18,7 +18,7 @@ services: fee-server: build: context: . - dockerfile: Dockerfile.Fee.server.dev + dockerfile: dockerfiles/Dockerfile.Fee.server.dev volumes: - ./cmd:/app/cmd - ./plugin:/app/plugin @@ -31,6 +31,38 @@ services: - ./fee.server.example.json:/app/config.json - ./etc/vultisig/fee.yml:/etc/vultisig/fee.yml + treasury-worker: + build: + context: . + dockerfile: dockerfiles/Dockerfile.Treasury.worker.dev + volumes: + - ./cmd:/app/cmd + - ./plugin:/app/plugin + - ./internal:/app/internal + - ./api:/app/api + - ./storage:/app/storage + - ./common:/app/common + - ./pkg:/app/pkg + - ./service:/app/service + - ./treasury.worker.example.json:/app/config.json + - ./etc/vultisig/treasury.yml:/etc/vultisig/treasury.yml + + treasury-server: + build: + context: . + dockerfile: dockerfiles/Dockerfile.Treasury.server.dev + volumes: + - ./cmd:/app/cmd + - ./plugin:/app/plugin + - ./internal:/app/internal + - ./api:/app/api + - ./storage:/app/storage + - ./common:/app/common + - ./pkg:/app/pkg + - ./service:/app/service + - ./treasury.server.example.json:/app/config.json + - ./etc/vultisig/treasury.yml:/etc/vultisig/treasury.yml + volumes: db_data: minio_data: diff --git a/docker-compose.yaml b/docker-compose.yaml index b4aa408..41a9b6d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -46,6 +46,20 @@ services: volumes: - redis_fees:/data + redis-treasury: + image: redis:latest + ports: + - "6376:6379" + networks: + - shared_network + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 1s + timeout: 5s + retries: 3 + volumes: + - redis_treasury:/data + asynqmon: platform: linux/amd64 image: hibiken/asynqmon:latest @@ -57,7 +71,7 @@ services: minio-plugin: build: context: . - dockerfile: Dockerfile.minio + dockerfile: dockerfiles/Dockerfile.minio platforms: - linux/amd64 ports: @@ -74,7 +88,7 @@ services: payroll-server: build: context: . - dockerfile: Dockerfile.Payroll.server + dockerfile: dockerfiles/Dockerfile.Payroll.server platforms: - linux/amd64 environment: @@ -97,7 +111,7 @@ services: payroll-worker: build: context: . - dockerfile: Dockerfile.Payroll.worker + dockerfile: dockerfiles/Dockerfile.Payroll.worker platforms: - linux/amd64 environment: @@ -118,7 +132,7 @@ services: payroll-scheduler: build: context: . - dockerfile: Dockerfile.Payroll.scheduler + dockerfile: dockerfiles/Dockerfile.Payroll.scheduler platforms: - linux/amd64 environment: @@ -139,7 +153,7 @@ services: fee-server: build: context: . - dockerfile: Dockerfile.Fee.server + dockerfile: dockerfiles/Dockerfile.Fee.server platforms: - linux/amd64 environment: @@ -162,7 +176,7 @@ services: fee-worker: build: context: . - dockerfile: Dockerfile.Fee.worker + dockerfile: dockerfiles/Dockerfile.Fee.worker platforms: - linux/amd64 environment: @@ -180,11 +194,58 @@ services: networks: - shared_network + treasury-server: + build: + context: . + dockerfile: dockerfiles/Dockerfile.Treasury.server + platforms: + - linux/amd64 + environment: + REDIS_HOST: redis-treasury + REDIS_PORT: 6379 + BLOCK_STORAGE_HOST: http://minio-plugin:9000 + DATABASE_DSN: postgres://myuser:mypassword@plugin-db:5432/vultisig-treasury?sslmode=disable + ports: + - "8083:8080" + depends_on: + plugin-db: + condition: service_healthy + redis-treasury: + condition: service_healthy + minio-plugin: + condition: service_started + networks: + - shared_network + + treasury-worker: + build: + context: . + dockerfile: dockerfiles/Dockerfile.Treasury.worker + platforms: + - linux/amd64 + environment: + REDIS_HOST: redis-treasury + REDIS_PORT: 6379 + BLOCK_STORAGE_HOST: http://minio-plugin:9000 + DATABASE_DSN: postgres://myuser:mypassword@plugin-db:5432/vultisig-treasury?sslmode=disable + depends_on: + plugin-db: + condition: service_healthy + redis-treasury: + condition: service_healthy + minio-plugin: + condition: service_started + networks: + - shared_network + + + volumes: db_data: minio_data: redis_payroll: redis_fees: + redis_treasury: networks: shared_network: external: true diff --git a/Dockerfile.Fee.server b/dockerfiles/Dockerfile.Fee.server similarity index 100% rename from Dockerfile.Fee.server rename to dockerfiles/Dockerfile.Fee.server diff --git a/Dockerfile.Fee.server.dev b/dockerfiles/Dockerfile.Fee.server.dev similarity index 100% rename from Dockerfile.Fee.server.dev rename to dockerfiles/Dockerfile.Fee.server.dev diff --git a/Dockerfile.Fee.worker b/dockerfiles/Dockerfile.Fee.worker similarity index 100% rename from Dockerfile.Fee.worker rename to dockerfiles/Dockerfile.Fee.worker diff --git a/Dockerfile.Fee.worker.dev b/dockerfiles/Dockerfile.Fee.worker.dev similarity index 100% rename from Dockerfile.Fee.worker.dev rename to dockerfiles/Dockerfile.Fee.worker.dev diff --git a/Dockerfile.Payroll.scheduler b/dockerfiles/Dockerfile.Payroll.scheduler similarity index 100% rename from Dockerfile.Payroll.scheduler rename to dockerfiles/Dockerfile.Payroll.scheduler diff --git a/Dockerfile.Payroll.server b/dockerfiles/Dockerfile.Payroll.server similarity index 100% rename from Dockerfile.Payroll.server rename to dockerfiles/Dockerfile.Payroll.server diff --git a/Dockerfile.Payroll.worker b/dockerfiles/Dockerfile.Payroll.worker similarity index 100% rename from Dockerfile.Payroll.worker rename to dockerfiles/Dockerfile.Payroll.worker diff --git a/dockerfiles/Dockerfile.Treasury.server b/dockerfiles/Dockerfile.Treasury.server new file mode 100644 index 0000000..d9c5356 --- /dev/null +++ b/dockerfiles/Dockerfile.Treasury.server @@ -0,0 +1,44 @@ +# Use the official Go image as the base image +FROM golang:1.24 as builder + +# Set the working directory +WORKDIR /app + +# Copy go.mod and go.sum to install dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the source code +COPY . . + +RUN wget https://github.com/vultisig/go-wrappers/archive/refs/heads/master.tar.gz +RUN tar -xzf master.tar.gz && \ + cd go-wrappers-master && \ + mkdir -p /usr/local/lib/dkls && \ + cp --recursive includes /usr/local/lib/dkls + +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux/:${LD_LIBRARY_PATH:-} + +# Build the application +RUN go build -o server ./cmd/treasury/server + +# Use a minimal base image for the final stage +FROM ubuntu:24.04 + +RUN apt-get update && \ + apt-get install -y wget && \ + rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy the built binary from the builder stage +COPY --from=builder /app/server . +COPY --from=builder /usr/local/lib/dkls /usr/local/lib/dkls +COPY treasury.server.example.json config.json +COPY ./etc/vultisig/treasury.yml /etc/vultisig/treasury.yml + +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux/:${LD_LIBRARY_PATH:-} + +# Run the application +CMD ["./server"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.Treasury.server.dev b/dockerfiles/Dockerfile.Treasury.server.dev new file mode 100644 index 0000000..4242ce6 --- /dev/null +++ b/dockerfiles/Dockerfile.Treasury.server.dev @@ -0,0 +1,41 @@ +# Use the official Go image as the base image +FROM golang:1.24 + +# Install air for hot reloading +RUN go install github.com/air-verse/air@latest + +# Set the working directory +WORKDIR /app + +# Copy go.mod and go.sum to install dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Download and install DKLS wrappers +RUN wget https://github.com/vultisig/go-wrappers/archive/refs/heads/master.tar.gz +RUN tar -xzf master.tar.gz && \ + cd go-wrappers-master && \ + mkdir -p /usr/local/lib/dkls && \ + cp --recursive includes /usr/local/lib/dkls + +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux/:${LD_LIBRARY_PATH:-} + +# Create tmp directory for air +RUN mkdir -p /app/tmp + +# Create air configuration +RUN echo 'root = "."' > .air.toml && \ + echo '' >> .air.toml && \ + echo '[build]' >> .air.toml && \ + echo ' bin = "./tmp/main"' >> .air.toml && \ + echo ' cmd = "go build -o ./tmp/main ./cmd/treasury/server"' >> .air.toml && \ + echo ' delay = 1000' >> .air.toml && \ + echo ' exclude_dir = ["assets", "tmp", "vendor", "testdata"]' >> .air.toml && \ + echo ' exclude_regex = ["_test.go"]' >> .air.toml + +# Copy configuration files +COPY treasury.server.example.json config.json +COPY ./etc/vultisig/treasury.yml /etc/vultisig/treasury.yml + +# Run air for hot reloading +CMD ["air"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.Treasury.worker b/dockerfiles/Dockerfile.Treasury.worker new file mode 100644 index 0000000..942be50 --- /dev/null +++ b/dockerfiles/Dockerfile.Treasury.worker @@ -0,0 +1,44 @@ +# Use the official Go image as the base image +FROM golang:1.24 as builder + +# Set the working directory +WORKDIR /app + +# Copy go.mod and go.sum to install dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the source code +COPY . . + +RUN wget https://github.com/vultisig/go-wrappers/archive/refs/heads/master.tar.gz +RUN tar -xzf master.tar.gz && \ + cd go-wrappers-master && \ + mkdir -p /usr/local/lib/dkls && \ + cp --recursive includes /usr/local/lib/dkls + +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux/:${LD_LIBRARY_PATH:-} + +# Build the application +RUN go build -o worker ./cmd/treasury/worker + +# Use a minimal base image for the final stage +FROM ubuntu:24.04 + +RUN apt-get update && \ + apt-get install -y wget && \ + rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy the built binary from the builder stage +COPY --from=builder /app/worker . +COPY --from=builder /usr/local/lib/dkls /usr/local/lib/dkls +COPY treasury.worker.example.json config.json +COPY ./etc/vultisig/treasury.yml /etc/vultisig/treasury.yml + +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux/:${LD_LIBRARY_PATH:-} + +# Run the application +CMD ["./worker"] \ No newline at end of file diff --git a/dockerfiles/Dockerfile.Treasury.worker.dev b/dockerfiles/Dockerfile.Treasury.worker.dev new file mode 100644 index 0000000..81a2376 --- /dev/null +++ b/dockerfiles/Dockerfile.Treasury.worker.dev @@ -0,0 +1,41 @@ +# Use the official Go image as the base image +FROM golang:1.24 + +# Install air for hot reloading +RUN go install github.com/air-verse/air@latest + +# Set the working directory +WORKDIR /app + +# Copy go.mod and go.sum to install dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Download and install DKLS wrappers +RUN wget https://github.com/vultisig/go-wrappers/archive/refs/heads/master.tar.gz +RUN tar -xzf master.tar.gz && \ + cd go-wrappers-master && \ + mkdir -p /usr/local/lib/dkls && \ + cp --recursive includes /usr/local/lib/dkls + +ENV LD_LIBRARY_PATH=/usr/local/lib/dkls/includes/linux/:${LD_LIBRARY_PATH:-} + +# Create tmp directory for air +RUN mkdir -p /app/tmp + +# Create air configuration +RUN echo 'root = "."' > .air.toml && \ + echo '' >> .air.toml && \ + echo '[build]' >> .air.toml && \ + echo ' bin = "./tmp/main"' >> .air.toml && \ + echo ' cmd = "go build -o ./tmp/main ./cmd/treasury/worker"' >> .air.toml && \ + echo ' delay = 1000' >> .air.toml && \ + echo ' exclude_dir = ["assets", "tmp", "vendor", "testdata"]' >> .air.toml && \ + echo ' exclude_regex = ["_test.go"]' >> .air.toml + +# Copy configuration files +COPY treasury.worker.example.json config.json +COPY ./etc/vultisig/treasury.yml /etc/vultisig/treasury.yml + +# Run air for hot reloading +CMD ["air"] \ No newline at end of file diff --git a/Dockerfile.minio b/dockerfiles/Dockerfile.minio similarity index 100% rename from Dockerfile.minio rename to dockerfiles/Dockerfile.minio diff --git a/etc/vultisig/fee.yml b/etc/vultisig/fee.yml index 3d9d759..1486f67 100644 --- a/etc/vultisig/fee.yml +++ b/etc/vultisig/fee.yml @@ -2,7 +2,7 @@ type: fee version: 1.0.0 usdc_address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 verifier_token: localhost-fee-apikey -eth_provider: ${ETH_PROVIDER_URL} +eth_provider: https://eth-mainnet.g.alchemy.com/v2/HAtIwB5y82TNVwaHkwcMj chain_id: 1 jobs: load: diff --git a/etc/vultisig/treasury.yml b/etc/vultisig/treasury.yml new file mode 100644 index 0000000..6ef7bed --- /dev/null +++ b/etc/vultisig/treasury.yml @@ -0,0 +1,11 @@ +chain_id: 1 +eth_provider: https://eth-mainnet.g.alchemy.com/v2/HAtIwB5y82TNVwaHkwcMj +usdc_address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 +verifier_token: localhost-treasury-apikey +jobs: + load: + cronexpr: "@every 10s" + transact: + cronexpr: "@every 10s" + post: + cronexpr: "@every 10s" \ No newline at end of file diff --git a/init-scripts/01_create_vultisig_plugin.sql b/init-scripts/01_create_vultisig_plugin.sql index 4806dde..e954a9a 100644 --- a/init-scripts/01_create_vultisig_plugin.sql +++ b/init-scripts/01_create_vultisig_plugin.sql @@ -1,2 +1,3 @@ CREATE DATABASE "vultisig-payroll"; -CREATE DATABASE "vultisig-fee"; \ No newline at end of file +CREATE DATABASE "vultisig-fee"; +CREATE DATABASE "vultisig-treasury"; \ No newline at end of file diff --git a/plugin/fees/fees.go b/plugin/fees/fees.go index 87362b0..ccf2b8b 100644 --- a/plugin/fees/fees.go +++ b/plugin/fees/fees.go @@ -31,7 +31,6 @@ All key logic related to fees will go here, that includes var _ plugin.Spec = (*FeePlugin)(nil) type FeePlugin struct { - vaultService *vault.ManagementService vaultStorage *vault.BlockStorageImp signer *keysign.Signer db storage.DatabaseStorage diff --git a/plugin/treasury/config.go b/plugin/treasury/config.go new file mode 100644 index 0000000..52fe26a --- /dev/null +++ b/plugin/treasury/config.go @@ -0,0 +1,157 @@ +package treasury + +import ( + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/spf13/viper" + "github.com/vultisig/recipes/sdk/evm" +) + +type TreasuryConfig struct { + EthProvider *ethclient.Client + ChainId *big.Int + SDK *evm.SDK + EncryptionSecret string + Jobs struct { + Load struct { + Cronexpr string `mapstructure:"cronexpr"` + } `mapstructure:"load"` + Transact struct { + Cronexpr string `mapstructure:"cronexpr"` + } `mapstructure:"transact"` + Post struct { + Cronexpr string `mapstructure:"cronexpr"` + } `mapstructure:"post"` + } +} + +type TreasuryConfigWrapper struct { + TreasuryConfig `mapstructure:",squash"` + ChainIdRaw uint64 `mapstructure:"chain_id"` + EthProviderRaw string `mapstructure:"eth_provider"` +} + +type ConfigOption func(*TreasuryConfigWrapper) error + +func withDefaults(c *TreasuryConfigWrapper) { + c.ChainIdRaw = 1 + c.EthProviderRaw = "https://eth.public-rpc.com" + c.Jobs.Load.Cronexpr = "@every 10s" + c.Jobs.Transact.Cronexpr = "@every 10s" + c.Jobs.Post.Cronexpr = "@every 10s" +} + +func WithChainIdRaw(chainIdRaw uint64) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + c.ChainIdRaw = chainIdRaw + return nil + } +} + +func WithEthProviderRaw(ethProviderRaw string) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + c.EthProviderRaw = ethProviderRaw + return nil + } +} + +func WithEncryptionSecret(encryptionSecret string) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + c.EncryptionSecret = encryptionSecret + return nil + } +} + +func WithCronexpr(load, transact, post string) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + WithLoadTiming(load)(c) + WithTransactTiming(transact)(c) + WithPostTiming(post)(c) + return nil + } +} + +func WithLoadTiming(load string) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + c.Jobs.Load.Cronexpr = load + return nil + } +} + +func WithTransactTiming(transact string) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + c.Jobs.Transact.Cronexpr = transact + return nil + } +} + +func WithPostTiming(post string) ConfigOption { + return func(c *TreasuryConfigWrapper) error { + c.Jobs.Post.Cronexpr = post + return nil + } +} +func WithFileConfig(basePath string) ConfigOption { + + return func(c *TreasuryConfigWrapper) error { + v := viper.New() + v.SetConfigName("treasury") + + // Add config paths in order of precedence + if basePath != "" { + v.AddConfigPath(basePath) + } + v.AddConfigPath(".") + v.AddConfigPath("/etc/vultisig") + + // Enable environment variable overrides + v.AutomaticEnv() + v.SetEnvPrefix("TREASURY") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + + if err := v.ReadInConfig(); err != nil { + return fmt.Errorf("failed to read config: %w", err) + } + + var wrappedConfig TreasuryConfigWrapper + if err := v.Unmarshal(&wrappedConfig); err != nil { + return fmt.Errorf("failed to unmarshal config: %w", err) + } + *c = wrappedConfig + return nil + } +} + +func postConfigLoad(c *TreasuryConfigWrapper) error { + if c.ChainId == nil { + c.ChainId = big.NewInt(0).SetUint64(c.ChainIdRaw) + } + if c.EthProvider == nil { + var err error + c.EthProvider, err = ethclient.Dial(c.EthProviderRaw) + if err != nil { + return fmt.Errorf("failed to dial eth provider: %w", err) + } + } + if c.SDK == nil { + c.SDK = evm.NewSDK(c.ChainId, c.EthProvider, c.EthProvider.Client()) + } + return nil +} + +func NewTreasuryConfig(fns ...ConfigOption) (*TreasuryConfig, error) { + c := &TreasuryConfigWrapper{} + withDefaults(c) + for _, fn := range fns { + if err := fn(c); err != nil { + return nil, err + } + } + if err := postConfigLoad(c); err != nil { + return nil, err + } + return &c.TreasuryConfig, nil +} diff --git a/plugin/treasury/constants.go b/plugin/treasury/constants.go new file mode 100644 index 0000000..17bc3e2 --- /dev/null +++ b/plugin/treasury/constants.go @@ -0,0 +1,20 @@ +package treasury + +// Task Definitions +const TypeTreasuryLoad = "treasury:load" // Load list of pending fees into the db from the verifier +const TypeTreasuryTransact = "treasury:transaction" // Collect a list of loaded fees from the users wallet +const TypeTreasuryPostTx = "treasury:post_tx" // Check the status of the fee runs + +var ERC20_TRANSFER_GAS int = 65000 //typically the upper bound from an ERC20 transfer + +// ABIS +// TODO abis here for uniswap if no fees are present +const erc20ABI = `[{ + "name": "transfer", + "type": "function", + "inputs": [ + {"name": "recipient", "type": "address"}, + {"name": "amount", "type": "uint256"} + ], + "outputs": [{"name": "", "type": "bool"}] +}]` diff --git a/plugin/treasury/load.go b/plugin/treasury/load.go new file mode 100644 index 0000000..0c57a3f --- /dev/null +++ b/plugin/treasury/load.go @@ -0,0 +1,12 @@ +package treasury + +import ( + "context" + + "github.com/hibiken/asynq" +) + +func (tp *TreasuryPlugin) LoadTreasuryPayments(ctx context.Context, t *asynq.Task) error { + tp.logger.Info("Loading treasury payments") + return nil +} diff --git a/plugin/treasury/post.go b/plugin/treasury/post.go new file mode 100644 index 0000000..08750ca --- /dev/null +++ b/plugin/treasury/post.go @@ -0,0 +1,12 @@ +package treasury + +import ( + "context" + + "github.com/hibiken/asynq" +) + +func (tp *TreasuryPlugin) PostTreasuryPayments(ctx context.Context, t *asynq.Task) error { + tp.logger.Info("Posting treasury payments") + return nil +} diff --git a/plugin/treasury/transact.go b/plugin/treasury/transact.go new file mode 100644 index 0000000..2147079 --- /dev/null +++ b/plugin/treasury/transact.go @@ -0,0 +1,12 @@ +package treasury + +import ( + "context" + + "github.com/hibiken/asynq" +) + +func (tp *TreasuryPlugin) TransactTreasuryPayments(ctx context.Context, t *asynq.Task) error { + tp.logger.Info("Transacting treasury payments") + return nil +} diff --git a/plugin/treasury/treasury.go b/plugin/treasury/treasury.go new file mode 100644 index 0000000..599f24a --- /dev/null +++ b/plugin/treasury/treasury.go @@ -0,0 +1,77 @@ +package treasury + +import ( + "fmt" + "sync" + + "github.com/sirupsen/logrus" + "github.com/vultisig/plugin/internal/verifierapi" + "github.com/vultisig/plugin/storage" + "github.com/vultisig/recipes/sdk/evm" + rtypes "github.com/vultisig/recipes/types" + "github.com/vultisig/verifier/plugin" + "github.com/vultisig/verifier/plugin/keysign" + vtypes "github.com/vultisig/verifier/types" + "github.com/vultisig/verifier/vault" +) + +var _ plugin.Spec = (*TreasuryPlugin)(nil) + +type TreasuryPlugin struct { + config *TreasuryConfig + vaultStorage *vault.BlockStorageImp + signer *keysign.Signer + db storage.DatabaseStorage + eth *evm.SDK + logger logrus.FieldLogger + verifierApi *verifierapi.VerifierApi + jobMutex sync.Mutex // Prevents race conditions between jobs (load, transact, post etc) +} + +func NewTreasuryPlugin( + config *TreasuryConfig, + vaultStorage *vault.BlockStorageImp, + signer *keysign.Signer, + db storage.DatabaseStorage, + logger logrus.FieldLogger, + verifierApi *verifierapi.VerifierApi, +) (*TreasuryPlugin, error) { + var jobMutex sync.Mutex + if config == nil { + return nil, fmt.Errorf("config cannot be nil") + } + if vaultStorage == nil { + return nil, fmt.Errorf("vault storage cannot be nil") + } + if db == nil { + return nil, fmt.Errorf("database storage cannot be nil") + } + if logger == nil { + return nil, fmt.Errorf("logger cannot be nil") + } + if verifierApi == nil { + return nil, fmt.Errorf("verifier api cannot be nil") + } + + return &TreasuryPlugin{ + config: config, + vaultStorage: vaultStorage, + signer: signer, + db: db, + logger: logger, + verifierApi: verifierApi, + jobMutex: jobMutex, + }, nil +} + +func (tp *TreasuryPlugin) GetRecipeSpecification() (*rtypes.RecipeSchema, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (tp *TreasuryPlugin) ValidatePluginPolicy(policy vtypes.PluginPolicy) error { + return nil +} + +func (tp *TreasuryPlugin) Suggest(configuration map[string]any) (*rtypes.PolicySuggest, error) { + return nil, fmt.Errorf("unimplemented") +} diff --git a/treasury.server.example.json b/treasury.server.example.json new file mode 100644 index 0000000..5f2336a --- /dev/null +++ b/treasury.server.example.json @@ -0,0 +1,30 @@ +{ + "verifier": { + "url": "verifier:8080" + }, + "server": { + "host": "localhost", + "port": 8080, + "encryption_secret": "test123" + }, + "database": { + "dsn": "postgres://myuser:mypassword@plugin-db:5432/vultisig-treasury?sslmode=disable" + }, + "base_config_path": "../../../etc/vultisig", + "redis": { + "host": "redis-treasury", + "port": "6379", + "password": "password" + }, + "block_storage": { + "host": "http://minio-plugin:9000", + "region": "us-east-1", + "access_key": "minioadmin", + "secret": "minioadmin", + "bucket": "vultisig-treasury" + }, + "datadog": { + "host": "localhost", + "port": 8125 + } +} diff --git a/treasury.worker.example.json b/treasury.worker.example.json new file mode 100644 index 0000000..11e4720 --- /dev/null +++ b/treasury.worker.example.json @@ -0,0 +1,36 @@ +{ + "verifier": { + "url": "http://verifier:8080", + "token": "localhost-apikey", + "party_prefix": "verifier" + }, + "vault_service": { + "relay": { + "server": "https://api.vultisig.com/router" + }, + "local_party_prefix": "treasury-plugin-0000", + "queue_email_task": false, + "encryption_secret": "test123", + "do_setup_msg": true + }, + "redis": { + "host": "redis-treasury", + "port": "6379", + "password": "password" + }, + "block_storage": { + "host": "http://minio-plugin:9000", + "region": "us-east-1", + "access_key": "minioadmin", + "secret": "minioadmin", + "bucket": "vultisig-treasury" + }, + "datadog": { + "host": "localhost", + "port": 8125 + }, + "base_config_path": "../../../etc/vultisig", + "database": { + "dsn": "postgres://myuser:mypassword@plugin-db:5432/vultisig-treasury?sslmode=disable" + } +}