diff --git a/api/server.go b/api/server.go index e8ecab4..09e0ce3 100644 --- a/api/server.go +++ b/api/server.go @@ -16,11 +16,11 @@ import ( "github.com/sirupsen/logrus" vcommon "github.com/vultisig/verifier/common" "github.com/vultisig/verifier/plugin" + "github.com/vultisig/verifier/plugin/tasks" vtypes "github.com/vultisig/verifier/types" "github.com/vultisig/verifier/vault" "github.com/vultisig/plugin/internal/scheduler" - "github.com/vultisig/plugin/internal/tasks" vv "github.com/vultisig/plugin/internal/vultisig_validator" "github.com/vultisig/plugin/service" "github.com/vultisig/plugin/storage" diff --git a/cmd/dca/worker/main.go b/cmd/dca/worker/main.go index 5fb7b2e..c1cbb77 100644 --- a/cmd/dca/worker/main.go +++ b/cmd/dca/worker/main.go @@ -7,11 +7,10 @@ import ( "github.com/DataDog/datadog-go/statsd" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" + "github.com/vultisig/verifier/plugin/tasks" "github.com/vultisig/verifier/tx_indexer" "github.com/vultisig/verifier/tx_indexer/pkg/storage" "github.com/vultisig/verifier/vault" - - "github.com/vultisig/plugin/internal/tasks" ) func main() { diff --git a/cmd/fees/worker/main.go b/cmd/fees/worker/main.go index 7da9b84..97450b8 100644 --- a/cmd/fees/worker/main.go +++ b/cmd/fees/worker/main.go @@ -8,14 +8,14 @@ import ( "github.com/DataDog/datadog-go/statsd" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" - "github.com/vultisig/plugin/internal/keysign" + "github.com/vultisig/verifier/plugin/keysign" + "github.com/vultisig/verifier/plugin/tasks" "github.com/vultisig/verifier/tx_indexer" "github.com/vultisig/verifier/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/tasks" "github.com/vultisig/plugin/plugin/fees" "github.com/vultisig/plugin/storage/postgres" ) diff --git a/cmd/payroll/scheduler/main.go b/cmd/payroll/scheduler/main.go index 66b9a83..d025420 100644 --- a/cmd/payroll/scheduler/main.go +++ b/cmd/payroll/scheduler/main.go @@ -6,8 +6,10 @@ import ( "github.com/hibiken/asynq" "github.com/sirupsen/logrus" + + "github.com/vultisig/verifier/plugin/tasks" + "github.com/vultisig/plugin/internal/scheduler" - "github.com/vultisig/plugin/internal/tasks" "github.com/vultisig/plugin/plugin/payroll" "github.com/vultisig/plugin/storage/postgres" ) diff --git a/cmd/payroll/worker/main.go b/cmd/payroll/worker/main.go index 0cbb6c7..8afa51e 100644 --- a/cmd/payroll/worker/main.go +++ b/cmd/payroll/worker/main.go @@ -8,13 +8,13 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" - "github.com/vultisig/plugin/internal/keysign" + "github.com/vultisig/verifier/plugin/keysign" + "github.com/vultisig/verifier/plugin/tasks" "github.com/vultisig/verifier/tx_indexer" "github.com/vultisig/verifier/tx_indexer/pkg/storage" "github.com/vultisig/verifier/vault" "github.com/vultisig/vultiserver/relay" - "github.com/vultisig/plugin/internal/tasks" "github.com/vultisig/plugin/plugin/payroll" "github.com/vultisig/plugin/storage/postgres" ) diff --git a/go.mod b/go.mod index 10f0af6..c04745d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/google/uuid v1.6.0 github.com/hibiken/asynq v0.25.1 github.com/jackc/pgx/v5 v5.7.4 - github.com/kaptinlin/jsonschema v0.4.6 github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/pressly/goose/v3 v3.24.2 @@ -20,7 +19,7 @@ require ( github.com/vultisig/commondata v0.0.0-20250710214228-61d9ed8f7778 github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74 github.com/vultisig/recipes v0.0.0-20250729120802-9b1d07f8262a - github.com/vultisig/verifier v0.0.0-20250728211124-309f39dbdb80 + github.com/vultisig/verifier v0.0.0-20250731092019-00d44a4b02b9 github.com/vultisig/vultiserver v0.0.0-20250715212748-4b23f9849e4b golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 @@ -109,6 +108,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/kaptinlin/go-i18n v0.1.4 // indirect + github.com/kaptinlin/jsonschema v0.4.6 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/pretty v0.3.1 // indirect diff --git a/go.sum b/go.sum index 2554364..b35937c 100644 --- a/go.sum +++ b/go.sum @@ -759,8 +759,8 @@ github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74 h1:goqwk4n github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74/go.mod h1:nOykk4nOy1L3yXtLSlYvVsgizBnCQ3tR2N5uwGPdvaM= github.com/vultisig/recipes v0.0.0-20250729120802-9b1d07f8262a h1:KoAwytLj092KNgWHh0a5tcupsSm5HtentT0yUbDGWFQ= github.com/vultisig/recipes v0.0.0-20250729120802-9b1d07f8262a/go.mod h1:Ot3lrUnnSw67Hep+MelclVPgNLDxJP01Ezixw/1RYRE= -github.com/vultisig/verifier v0.0.0-20250728211124-309f39dbdb80 h1:aOgViLVMBV1E8VicCh2g9pky0I+NXLlWNvzDm5c1HBk= -github.com/vultisig/verifier v0.0.0-20250728211124-309f39dbdb80/go.mod h1:eK+KyWZSgiB/gzDJjsDKx+ry7gtJadplzpMNF4gvkKk= +github.com/vultisig/verifier v0.0.0-20250731092019-00d44a4b02b9 h1:j4duFCHur0x8vtAh3YHcwmRZKqMwcEkl/u7wjOBrMbc= +github.com/vultisig/verifier v0.0.0-20250731092019-00d44a4b02b9/go.mod h1:nFUize5jJAi5P7V0dloNUB+ykJZvgNe4S80h6uAA9SQ= github.com/vultisig/vultiserver v0.0.0-20250715212748-4b23f9849e4b h1:Ed2DOWo8fA0KG6e36rzUmGpxcOQjmWTbxWyvUbI5by8= github.com/vultisig/vultiserver v0.0.0-20250715212748-4b23f9849e4b/go.mod h1:HwP2IgW6Mcu/gX8paFuKvfibrGE9UmPgkOFTub6dskM= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= diff --git a/internal/keysign/api_emitter.go b/internal/keysign/api_emitter.go deleted file mode 100644 index 67fe7bb..0000000 --- a/internal/keysign/api_emitter.go +++ /dev/null @@ -1,44 +0,0 @@ -package keysign - -import ( - "context" - "fmt" - "net/http" - - "github.com/vultisig/plugin/internal/libhttp" - "github.com/vultisig/verifier/types" -) - -func NewVerifierEmitter(url, token string) Emitter { - return newApiEmitter[string]( - http.MethodPost, - url+"/plugin-signer/sign", - map[string]string{ - "Authorization": "Bearer " + token, - "Content-Type": "application/json", - }, - ) -} - -type apiEmitter[T comparable] struct { - method string - endpoint string - headers map[string]string -} - -// T is response type from the HTTP API call -func newApiEmitter[T comparable](method, endpoint string, headers map[string]string) *apiEmitter[T] { - return &apiEmitter[T]{ - method: method, - endpoint: endpoint, - headers: headers, - } -} - -func (e *apiEmitter[T]) Sign(ctx context.Context, req types.PluginKeysignRequest) error { - _, err := libhttp.Call[T](ctx, e.method, e.endpoint, e.headers, req, nil) - if err != nil { - return fmt.Errorf("failed to make API call: %w", err) - } - return nil -} diff --git a/internal/keysign/plugin_emitter.go b/internal/keysign/plugin_emitter.go deleted file mode 100644 index 292c5d9..0000000 --- a/internal/keysign/plugin_emitter.go +++ /dev/null @@ -1,45 +0,0 @@ -package keysign - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/hibiken/asynq" - "github.com/vultisig/verifier/types" -) - -type PluginEmitter struct { - client *asynq.Client - task string - queue string -} - -func NewPluginEmitter(client *asynq.Client, task, queue string) *PluginEmitter { - return &PluginEmitter{ - client: client, - task: task, - queue: queue, - } -} - -func (e *PluginEmitter) Sign(ctx context.Context, req types.PluginKeysignRequest) error { - buf, err := json.Marshal(req) - if err != nil { - return fmt.Errorf("failed to marshal JSON: %w", err) - } - - _, err = e.client.EnqueueContext( - ctx, - asynq.NewTask(e.task, buf), - asynq.MaxRetry(0), - asynq.Timeout(5*time.Minute), - asynq.Retention(10*time.Minute), - asynq.Queue(e.queue), - ) - if err != nil { - return fmt.Errorf("failed to enqueue task: %w", err) - } - return nil -} diff --git a/internal/keysign/signer.go b/internal/keysign/signer.go deleted file mode 100644 index c70890a..0000000 --- a/internal/keysign/signer.go +++ /dev/null @@ -1,222 +0,0 @@ -package keysign - -import ( - "context" - "crypto/md5" - "encoding/hex" - "errors" - "fmt" - "strings" - "time" - - "github.com/google/uuid" - "github.com/sirupsen/logrus" - "github.com/vultisig/mobile-tss-lib/tss" - "github.com/vultisig/verifier/types" - "github.com/vultisig/vultiserver/relay" -) - -// Emitter -// e.g. verifier API /plugin-signer/sign endpoint which puts to verifier.worker queue -// e.g. queue for a plugin.worker -// check interface implementation usages for examples -type Emitter interface { - Sign(ctx context.Context, req types.PluginKeysignRequest) error -} - -type Signer struct { - logger *logrus.Logger - relay *relay.Client - emitters []Emitter - partiesPrefixes []string -} - -func NewSigner( - logger *logrus.Logger, - relay *relay.Client, - emitters []Emitter, - partiesPrefixesRaw []string, -) *Signer { - var partiesPrefixes []string - for _, prefix := range partiesPrefixesRaw { - partiesPrefixes = append(partiesPrefixes, prefix+"-") - } - - return &Signer{ - logger: logger, - relay: relay, - emitters: emitters, - partiesPrefixes: partiesPrefixes, - } -} - -func (s *Signer) genIDs(req types.PluginKeysignRequest) (types.PluginKeysignRequest, error) { - // single place to generate, to avoid misusage/empty in plugin implementation - - if req.SessionID != "" { - return types.PluginKeysignRequest{}, errors.New("SessionID must be empty") - } - req.SessionID = uuid.New().String() - - if req.HexEncryptionKey != "" { - return types.PluginKeysignRequest{}, errors.New("HexEncryptionKey must be empty") - } - rnd, err := uuid.New().MarshalBinary() - if err != nil { - return types.PluginKeysignRequest{}, fmt.Errorf("failed to marshal UUID: %w", err) - } - req.HexEncryptionKey = hex.EncodeToString(rnd) - - return req, nil -} - -func (s *Signer) Sign( - ctx context.Context, - reqRaw types.PluginKeysignRequest, -) (map[string]tss.KeysignResponse, error) { - req, err := s.genIDs(reqRaw) - if err != nil { - return nil, fmt.Errorf("failed to generate IDs: %w", err) - } - - for _, emitter := range s.emitters { - err := emitter.Sign(ctx, req) - if err != nil { - return nil, fmt.Errorf("failed to sign with emitter: %w", err) - } - } - - partyIDs, err := s.waitPartiesAndStart(ctx, req.SessionID, s.partiesPrefixes) - if err != nil { - return nil, fmt.Errorf("failed to wait for parties and start: %w", err) - } - - var messages []string - for _, msg := range req.Messages { - messages = append(messages, msg.Message) - } - - res, err := s.waitResult(ctx, req.SessionID, partyIDs, req) - if err != nil { - return nil, fmt.Errorf("failed to wait for result: %w", err) - } - return res, nil -} - -func (s *Signer) waitResult( - ctx context.Context, - sessionID string, - partyIDs []string, - req types.PluginKeysignRequest, -) (map[string]tss.KeysignResponse, error) { - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(time.Second): - ok, err := s.relay.CheckCompletedParties(sessionID, partyIDs) - if err != nil { - return nil, fmt.Errorf("failed to check completed parties: %w", err) - } - if !ok { - s.logger.WithFields(logrus.Fields{ - "sessionID": sessionID, - "partyIDs": partyIDs, - }).Info("Waiting for parties to complete sign") - continue - } - - sigs := make(map[string]tss.KeysignResponse, len(req.Messages)) - for _, msg := range req.Messages { - md5Hash := md5.Sum([]byte(msg.Message)) - messageID := hex.EncodeToString(md5Hash[:]) - - sig, completeErr := s.relay.CheckKeysignComplete(sessionID, messageID) - if completeErr != nil { - s.logger.WithFields(logrus.Fields{ - "sessionID": sessionID, - "messageID": messageID, - "partyIDs": partyIDs, - }).WithError(completeErr).Info("continue polling: CheckKeysignComplete") - continue - } - if sig == nil { - return nil, fmt.Errorf( - "unexpected empty sig: messageID: %s, sessionID: %s", - messageID, - sessionID, - ) - } - sigs[msg.Hash] = *sig - } - return sigs, nil - } - } -} - -func (s *Signer) waitPartiesAndStart( - ctx context.Context, - sessionID string, - partiesPrefixes []string, -) ([]string, error) { - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(time.Second): - partiesJoined, err := s.relay.GetSession(sessionID) - if err != nil { - return nil, fmt.Errorf("failed to get session: %w", err) - } - - partiesIDs := filterIDsByPrefixes(partiesJoined, partiesPrefixes) - if len(partiesIDs) < len(partiesPrefixes) { - s.logger.WithFields(logrus.Fields{ - "sessionID": sessionID, - "partiesJoined": partiesIDs, - "partiesPrefixes": partiesPrefixes, - }).Info("Waiting for more parties to join") - continue - } - if len(partiesIDs) > len(partiesPrefixes) { - return nil, fmt.Errorf( - "too many parties joined: [%s], expected prefixes: [%s],"+ - " it may be caused by a bug in calling code", - strings.Join(partiesIDs, ","), - strings.Join(partiesPrefixes, ","), - ) - } - - s.logger.WithFields(logrus.Fields{ - "sessionID": sessionID, - "partiesJoined": partiesIDs, - "partiesPrefixes": partiesPrefixes, - }).Info("all expected parties joined") - - err = s.relay.StartSession(sessionID, partiesIDs) - if err != nil { - return nil, fmt.Errorf("failed to start session: %w", err) - } - return partiesIDs, nil - } - } -} - -func filterIDsByPrefixes(fullIDs, prefixes []string) []string { - seen := make(map[string]struct{}) - var result []string - - for _, id := range fullIDs { - for _, prefix := range prefixes { - if strings.HasPrefix(id, prefix) { - if _, exists := seen[id]; !exists { - seen[id] = struct{}{} - result = append(result, id) - } - break - } - } - } - - return result -} diff --git a/internal/libhttp/libhttp.go b/internal/libhttp/libhttp.go deleted file mode 100644 index e502f1a..0000000 --- a/internal/libhttp/libhttp.go +++ /dev/null @@ -1,72 +0,0 @@ -package libhttp - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - stdurl "net/url" -) - -func Call[T comparable]( - ctx context.Context, - method, url string, - headers map[string]string, - body interface{}, - query map[string]string, -) (T, error) { - b, err := json.Marshal(body) - if err != nil { - return *new(T), fmt.Errorf("failed to marshal request json: %w", err) - } - - var q string - if query != nil { - qurl := stdurl.Values{} - for k, v := range query { - qurl.Set(k, v) - } - q = "?" + qurl.Encode() - } - - req, err := http.NewRequestWithContext(ctx, method, url+q, bytes.NewReader(b)) - if err != nil { - return *new(T), fmt.Errorf("failed to build http request: %w", err) - } - - for k, v := range headers { - req.Header.Set(k, v) - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return *new(T), fmt.Errorf("failed to make http call: %w", err) - } - - bodyBytes, err := io.ReadAll(res.Body) - defer func() { - _ = res.Body.Close() - }() - if err != nil { - return *new(T), fmt.Errorf("failed to read response body: %w", err) - } - if res.StatusCode != http.StatusOK { - return *new(T), fmt.Errorf("failed to get successful response: status_code: %d, res_body: %s", res.StatusCode, string(bodyBytes)) - } - - _, isString := any(new(T)).(string) - if isString { - // for string response type no need to unmarshal JSON - return any(string(bodyBytes)).(T), nil - } - - var r T - err = json.Unmarshal(bodyBytes, &r) - if err != nil { - return *new(T), fmt.Errorf("failed to unmarshal response json: %w", err) - } - - return r, nil -} diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go deleted file mode 100644 index 75ab79e..0000000 --- a/internal/plugin/plugin.go +++ /dev/null @@ -1,51 +0,0 @@ -package plugin - -import ( - "encoding/base64" - "encoding/json" - "fmt" - - "github.com/kaptinlin/jsonschema" - "github.com/vultisig/recipes/engine" - rtypes "github.com/vultisig/recipes/types" - vtypes "github.com/vultisig/verifier/types" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" -) - -func ValidatePluginPolicy(policyDoc vtypes.PluginPolicy, spec *rtypes.RecipeSchema) error { - policyBytes, err := base64.StdEncoding.DecodeString(policyDoc.Recipe) - if err != nil { - return fmt.Errorf("failed to decode policy recipe: %w", err) - } - - var rPolicy rtypes.Policy - err = proto.Unmarshal(policyBytes, &rPolicy) - if err != nil { - return fmt.Errorf("failed to unmarshal policy: %w", err) - } - - err = engine.NewEngine().ValidatePolicyWithSchema(&rPolicy, spec) - if err != nil { - return fmt.Errorf("failed to validate policy: %w", err) - } - return nil -} - -func RecipeConfiguration(jsonSchema map[string]any) (*structpb.Struct, error) { - b, err := json.Marshal(jsonSchema) - if err != nil { - return nil, fmt.Errorf("failed to marshal schema: %w", err) - } - - _, err = jsonschema.NewCompiler().Compile(b) - if err != nil { - return nil, fmt.Errorf("failed to compile schema: %w", err) - } - - pb, err := structpb.NewStruct(jsonSchema) - if err != nil { - return nil, fmt.Errorf("failed to build pb schema: %w", err) - } - return pb, nil -} diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go deleted file mode 100644 index 314a3bb..0000000 --- a/internal/tasks/tasks.go +++ /dev/null @@ -1,35 +0,0 @@ -package tasks - -import ( - "fmt" - - "github.com/hibiken/asynq" -) - -const QUEUE_NAME = "plugin_queue" -const ( - TypePluginTransaction = "plugin:transaction" - TypeKeySignDKLS = "key:signDKLS" - TypeReshareDKLS = "key:reshareDKLS" -) - -func GetTaskResult(inspector *asynq.Inspector, taskID string) ([]byte, error) { - task, err := inspector.GetTaskInfo(QUEUE_NAME, taskID) - if err != nil { - return nil, fmt.Errorf("fail to find task, err: %w", err) - } - - if task == nil { - return nil, fmt.Errorf("task not found") - } - - if task.State == asynq.TaskStatePending { - return nil, fmt.Errorf("task is still in progress") - } - - if task.State == asynq.TaskStateCompleted { - return task.Result, nil - } - - return nil, fmt.Errorf("task state is invalid") -} diff --git a/plugin/fees/fees.go b/plugin/fees/fees.go index 7e90bb9..cda94dc 100644 --- a/plugin/fees/fees.go +++ b/plugin/fees/fees.go @@ -9,9 +9,9 @@ import ( "github.com/google/uuid" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" - "github.com/vultisig/plugin/internal/keysign" vcommon "github.com/vultisig/verifier/common" "github.com/vultisig/verifier/plugin" + "github.com/vultisig/verifier/plugin/keysign" "github.com/vultisig/verifier/tx_indexer" vtypes "github.com/vultisig/verifier/types" "golang.org/x/sync/errgroup" diff --git a/plugin/fees/policy.go b/plugin/fees/policy.go index 8f92713..181ac5b 100644 --- a/plugin/fees/policy.go +++ b/plugin/fees/policy.go @@ -3,8 +3,8 @@ package fees import ( "fmt" - "github.com/vultisig/plugin/internal/plugin" rtypes "github.com/vultisig/recipes/types" + "github.com/vultisig/verifier/plugin" vtypes "github.com/vultisig/verifier/types" ) diff --git a/plugin/payroll/payroll.go b/plugin/payroll/payroll.go index d04cfaf..5126560 100644 --- a/plugin/payroll/payroll.go +++ b/plugin/payroll/payroll.go @@ -6,11 +6,11 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" - "github.com/vultisig/plugin/internal/keysign" "github.com/vultisig/plugin/storage" "github.com/vultisig/recipes/sdk/evm" "github.com/vultisig/verifier/common" "github.com/vultisig/verifier/plugin" + "github.com/vultisig/verifier/plugin/keysign" "github.com/vultisig/verifier/tx_indexer" "github.com/vultisig/verifier/vault" ) diff --git a/plugin/payroll/policy.go b/plugin/payroll/policy.go index 5e1e57e..15952fe 100644 --- a/plugin/payroll/policy.go +++ b/plugin/payroll/policy.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/vultisig/plugin/internal/plugin" "github.com/vultisig/recipes/chain" "github.com/vultisig/recipes/engine" + "github.com/vultisig/verifier/plugin" vtypes "github.com/vultisig/verifier/types" ) diff --git a/plugin/payroll/transaction.go b/plugin/payroll/transaction.go index c7d8786..81f08f3 100644 --- a/plugin/payroll/transaction.go +++ b/plugin/payroll/transaction.go @@ -15,18 +15,19 @@ import ( "github.com/hibiken/asynq" "github.com/sirupsen/logrus" "github.com/vultisig/mobile-tss-lib/tss" - "github.com/vultisig/plugin/common" - "github.com/vultisig/plugin/internal/plugin" "github.com/vultisig/plugin/internal/scheduler" "github.com/vultisig/recipes/ethereum" "github.com/vultisig/recipes/sdk/evm" rtypes "github.com/vultisig/recipes/types" "github.com/vultisig/verifier/address" vcommon "github.com/vultisig/verifier/common" + "github.com/vultisig/verifier/plugin" "github.com/vultisig/verifier/tx_indexer/pkg/storage" vtypes "github.com/vultisig/verifier/types" "github.com/vultisig/vultiserver/contexthelper" "golang.org/x/sync/errgroup" + + "github.com/vultisig/plugin/common" ) func (p *Plugin) HandleSchedulerTrigger(c context.Context, t *asynq.Task) error {