diff --git a/cmd/service/api.go b/cmd/service/api.go index 9fa2738..9f83c9b 100644 --- a/cmd/service/api.go +++ b/cmd/service/api.go @@ -1,9 +1,13 @@ package main import ( + "errors" + "net/http" + "github.com/VictoriaMetrics/metrics" "github.com/go-playground/validator" "github.com/grassrootseconomics/cic-custodial/internal/api" + "github.com/hibiken/asynq" "github.com/labstack/echo/v4" ) @@ -14,6 +18,27 @@ func initApiServer(custodialContainer *custodial) *echo.Echo { server.HideBanner = true server.HidePort = true + server.HTTPErrorHandler = func(err error, c echo.Context) { + // Handle asynq duplication errors across all api handlers. + if errors.Is(err, asynq.ErrTaskIDConflict) { + c.JSON(http.StatusForbidden, api.ErrResp{ + Ok: false, + Code: api.DUPLICATE_ERROR, + Message: "Request with duplicate tracking id submitted.", + }) + return + } + + // Log internal server error for further investigation. + lo.Error("api:", "path", c.Path(), "err", err) + + c.JSON(http.StatusInternalServerError, api.ErrResp{ + Ok: false, + Code: api.INTERNAL_ERROR, + Message: "Internal server error.", + }) + } + if ko.Bool("service.metrics") { server.GET("/metrics", func(c echo.Context) error { metrics.WritePrometheus(c.Response(), true) diff --git a/config.toml b/config.toml index b67b525..1d5440e 100644 --- a/config.toml +++ b/config.toml @@ -9,8 +9,8 @@ metrics = true [system] # The giftable token is a training voucher # Every new user is given 5 DGFT -gas_faucet = "0x6dE38F79Cf455339e8141D15E208ba09eea634e1" -giftable_token_address = "0x4091fc149522d5FE31d0970687078B1aE0625892" +gas_faucet = "0xA8b3Ffc715e85792FB361BDee9357B38D5A4a6cF" +giftable_token_address = "0xdD4F5ea484F6b16f031eF7B98F3810365493BC20" giftable_token_value = 5000000 gas_refill_threshold = 100000000000000000 gas_refill_value = 100000000000000000 @@ -18,12 +18,12 @@ gas_refill_value = 100000000000000000 giftable_gas_value = 2000000000000000000 # System private key # Should always be toped up -private_key = "d87f322629cf071ccd3ddf17ab5e1abf098a25c4c5d791d7535d265379b2ae37" +private_key = "bfa7222a7bea3bde312434abe819b14cf3bc8703ceaabb98a9e3a97ceb0b79fd" lock_prefix = "lock:" -public_key = "0xe5ab7A5af1f28aA8E9658AC33a6ebF2a8641d948" +public_key = "0x08eb3a90128D5874da54cf654fCfA88cEd1bb047" token_decimals = 6 token_transfer_gas_limit = 200000 -account_index = "0x70138F458Fa56C034acb19E38d082843327F18A4" +account_index = "0xdb2550ac5E52A54B6189FFAf17ECfF33AE190db9" [chain] rpc_endpoint = "http://192.168.0.101:8545" diff --git a/internal/api/account.go b/internal/api/account.go index 06ba08d..7aeb756 100644 --- a/internal/api/account.go +++ b/internal/api/account.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" + "github.com/google/uuid" "github.com/grassrootseconomics/cic-custodial/internal/keystore" "github.com/grassrootseconomics/cic-custodial/internal/tasker" "github.com/grassrootseconomics/cic-custodial/internal/tasker/task" @@ -13,76 +14,50 @@ import ( // CreateAccountHandler route. // POST: /api/account/create -// JSON Body: -// trackingId -> Unique string // Returns the public key. func CreateAccountHandler( keystore keystore.Keystore, taskerClient *tasker.TaskerClient, ) func(echo.Context) error { return func(c echo.Context) error { - var accountRequest struct { - TrackingId string `json:"trackingId" validate:"required"` - } - - if err := c.Bind(&accountRequest); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) - } - - if err := c.Validate(accountRequest); err != nil { - return err - } + trackingId := uuid.NewString() generatedKeyPair, err := keypair.Generate() if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } id, err := keystore.WriteKeyPair(c.Request().Context(), generatedKeyPair) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } taskPayload, err := json.Marshal(task.AccountPayload{ PublicKey: generatedKeyPair.Public, - TrackingId: accountRequest.TrackingId, + TrackingId: trackingId, }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } _, err = taskerClient.CreateTask( tasker.PrepareAccountTask, tasker.DefaultPriority, &tasker.Task{ - Id: accountRequest.TrackingId, + Id: trackingId, Payload: taskPayload, }, ) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } - return c.JSON(http.StatusOK, okResp{ + return c.JSON(http.StatusOK, OkResp{ Ok: true, Result: H{ "publicKey": generatedKeyPair.Public, "custodialId": id, + "trackingId": trackingId, }, }) } diff --git a/internal/api/sign.go b/internal/api/sign.go index 492cf42..78d4397 100644 --- a/internal/api/sign.go +++ b/internal/api/sign.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" + "github.com/google/uuid" "github.com/grassrootseconomics/cic-custodial/internal/tasker" "github.com/labstack/echo/v4" ) @@ -22,8 +23,9 @@ func SignTransferHandler( taskerClient *tasker.TaskerClient, ) func(echo.Context) error { return func(c echo.Context) error { + trackingId := uuid.NewString() + var transferRequest struct { - TrackingId string `json:"trackingId" validate:"required"` From string `json:"from" validate:"required,eth_addr"` To string `json:"to" validate:"required,eth_addr"` VoucherAddress string `json:"voucherAddress" validate:"required,eth_addr"` @@ -31,10 +33,7 @@ func SignTransferHandler( } if err := c.Bind(&transferRequest); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } if err := c.Validate(transferRequest); err != nil { @@ -43,29 +42,26 @@ func SignTransferHandler( taskPayload, err := json.Marshal(transferRequest) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } _, err = taskerClient.CreateTask( tasker.SignTransferTask, tasker.HighPriority, &tasker.Task{ - Id: transferRequest.TrackingId, + Id: trackingId, Payload: taskPayload, }, ) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, errResp{ - Ok: false, - Code: INTERNAL_ERROR, - }) + return err } - return c.JSON(http.StatusOK, okResp{ + return c.JSON(http.StatusOK, OkResp{ Ok: true, + Result: H{ + "trackingId": trackingId, + }, }) } } diff --git a/internal/api/types.go b/internal/api/types.go index 0925cfc..cc236d5 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -3,16 +3,18 @@ package api const ( INTERNAL_ERROR = "ERR_INTERNAL" VALIDATION_ERROR = "ERR_VALIDATE" + DUPLICATE_ERROR = "ERR_DUPLICATE" ) type H map[string]any -type okResp struct { +type OkResp struct { Ok bool `json:"ok"` Result H `json:"result"` } -type errResp struct { - Ok bool `json:"ok"` - Code string `json:"errorCode"` +type ErrResp struct { + Ok bool `json:"ok"` + Code string `json:"errorCode"` + Message string `json:"message"` } diff --git a/internal/api/validator.go b/internal/api/validator.go index b6c7d84..b2be293 100644 --- a/internal/api/validator.go +++ b/internal/api/validator.go @@ -13,7 +13,7 @@ type Validator struct { func (v *Validator) Validate(i interface{}) error { if err := v.ValidatorProvider.Struct(i); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, errResp{ + return echo.NewHTTPError(http.StatusBadRequest, ErrResp{ Ok: false, Code: VALIDATION_ERROR, })