mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 18:26:16 +00:00
feat: isolate manual verification RPC usage
- add rpc node purpose support for general/manual_verify/both - keep scan, WSS and health checks away from manual_verify nodes - fallback manual hash verification from general RPC to manual_verify RPC - add public cashier hash submission path and errno responses - update tests for RPC selection, failover and manual payment flows
This commit is contained in:
@@ -1,25 +1,28 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/task"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// CreateRpcNodeRequest is the payload for creating an RPC node.
|
||||
type CreateRpcNodeRequest struct {
|
||||
Network string `json:"network" validate:"required" example:"tron"`
|
||||
Url string `json:"url" validate:"required" example:"https://api.trongrid.io"`
|
||||
Url string `json:"url" validate:"required" example:"https://api.trongrid.io"`
|
||||
// 连接类型 http=HTTP请求 ws=WebSocket长连接
|
||||
Type string `json:"type" validate:"required|in:http,ws" enums:"http,ws" example:"http"`
|
||||
Type string `json:"type" validate:"required|in:http,ws" enums:"http,ws" example:"http"`
|
||||
Weight int `json:"weight" example:"1"`
|
||||
ApiKey string `json:"api_key" example:""`
|
||||
Enabled *bool `json:"enabled" example:"true"`
|
||||
// 用途 general=通用 manual_verify=补单专用 both=通用+补单
|
||||
Purpose string `json:"purpose" enums:"general,manual_verify,both" example:"general"`
|
||||
}
|
||||
|
||||
// UpdateRpcNodeRequest is the payload for updating an RPC node.
|
||||
@@ -28,6 +31,7 @@ type UpdateRpcNodeRequest struct {
|
||||
Weight *int `json:"weight" example:"1"`
|
||||
ApiKey *string `json:"api_key" example:"your-api-key"`
|
||||
Enabled *bool `json:"enabled" example:"true"`
|
||||
Purpose *string `json:"purpose" enums:"general,manual_verify,both" example:"manual_verify"`
|
||||
}
|
||||
|
||||
// ListRpcNodes returns rows optionally filtered by network.
|
||||
@@ -64,7 +68,7 @@ func (c *BaseAdminController) ListRpcNodes(ctx echo.Context) error {
|
||||
func (c *BaseAdminController) CreateRpcNode(ctx echo.Context) error {
|
||||
req := new(CreateRpcNodeRequest)
|
||||
if err := ctx.Bind(req); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
if err := c.ValidateStruct(ctx, req); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
@@ -77,13 +81,23 @@ func (c *BaseAdminController) CreateRpcNode(ctx echo.Context) error {
|
||||
if req.Enabled != nil {
|
||||
enabled = *req.Enabled
|
||||
}
|
||||
purpose, err := rpcNodePurposeFromRequest(req.Purpose)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
nodeURL := strings.TrimSpace(req.Url)
|
||||
nodeType := strings.ToLower(strings.TrimSpace(req.Type))
|
||||
if err := validateRpcNodeURLForType(nodeURL, nodeType); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
row := &mdb.RpcNode{
|
||||
Network: strings.ToLower(strings.TrimSpace(req.Network)),
|
||||
Url: strings.TrimSpace(req.Url),
|
||||
Type: strings.ToLower(strings.TrimSpace(req.Type)),
|
||||
Url: nodeURL,
|
||||
Type: nodeType,
|
||||
Weight: weight,
|
||||
ApiKey: req.ApiKey,
|
||||
Enabled: enabled,
|
||||
Purpose: purpose,
|
||||
Status: mdb.RpcNodeStatusUnknown,
|
||||
LastLatencyMs: -1,
|
||||
}
|
||||
@@ -93,7 +107,7 @@ func (c *BaseAdminController) CreateRpcNode(ctx echo.Context) error {
|
||||
return c.SucJson(ctx, row)
|
||||
}
|
||||
|
||||
// UpdateRpcNode patches url/weight/api_key/enabled.
|
||||
// UpdateRpcNode patches url/weight/api_key/enabled/purpose.
|
||||
// @Summary Update RPC node
|
||||
// @Description Patch RPC node fields
|
||||
// @Tags Admin RPC Nodes
|
||||
@@ -108,15 +122,26 @@ func (c *BaseAdminController) CreateRpcNode(ctx echo.Context) error {
|
||||
func (c *BaseAdminController) UpdateRpcNode(ctx echo.Context) error {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
req := new(UpdateRpcNodeRequest)
|
||||
if err := ctx.Bind(req); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
fields := map[string]interface{}{}
|
||||
if req.Url != nil {
|
||||
fields["url"] = *req.Url
|
||||
row, err := data.GetRpcNodeByID(id)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if row.ID == 0 {
|
||||
return c.FailJson(ctx, constant.RpcNodeNotFoundErr)
|
||||
}
|
||||
nodeURL := strings.TrimSpace(*req.Url)
|
||||
if err := validateRpcNodeURLForType(nodeURL, row.Type); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
fields["url"] = nodeURL
|
||||
}
|
||||
if req.Weight != nil {
|
||||
fields["weight"] = *req.Weight
|
||||
@@ -127,6 +152,13 @@ func (c *BaseAdminController) UpdateRpcNode(ctx echo.Context) error {
|
||||
if req.Enabled != nil {
|
||||
fields["enabled"] = *req.Enabled
|
||||
}
|
||||
if req.Purpose != nil {
|
||||
purpose, err := rpcNodePurposeFromRequest(*req.Purpose)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
fields["purpose"] = purpose
|
||||
}
|
||||
if err := data.UpdateRpcNodeFields(id, fields); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
@@ -146,7 +178,7 @@ func (c *BaseAdminController) UpdateRpcNode(ctx echo.Context) error {
|
||||
func (c *BaseAdminController) DeleteRpcNode(ctx echo.Context) error {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
if err := data.DeleteRpcNodeByID(id); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
@@ -170,14 +202,14 @@ func (c *BaseAdminController) DeleteRpcNode(ctx echo.Context) error {
|
||||
func (c *BaseAdminController) HealthCheckRpcNode(ctx echo.Context) error {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
row, err := data.GetRpcNodeByID(id)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if row.ID == 0 {
|
||||
return c.FailJson(ctx, errors.New("node not found"))
|
||||
return c.FailJson(ctx, constant.RpcNodeNotFoundErr)
|
||||
}
|
||||
status, latency := task.ProbeNode(row.Url)
|
||||
if err := data.UpdateRpcNodeHealth(id, status, latency); err != nil {
|
||||
@@ -188,3 +220,40 @@ func (c *BaseAdminController) HealthCheckRpcNode(ctx echo.Context) error {
|
||||
"last_latency_ms": latency,
|
||||
})
|
||||
}
|
||||
|
||||
func rpcNodePurposeFromRequest(raw string) (string, error) {
|
||||
purpose := strings.ToLower(strings.TrimSpace(raw))
|
||||
if purpose == "" {
|
||||
return mdb.RpcNodePurposeGeneral, nil
|
||||
}
|
||||
switch purpose {
|
||||
case mdb.RpcNodePurposeGeneral, mdb.RpcNodePurposeManualVerify, mdb.RpcNodePurposeBoth:
|
||||
return purpose, nil
|
||||
default:
|
||||
return "", constant.RpcNodePurposeErr
|
||||
}
|
||||
}
|
||||
|
||||
func validateRpcNodeURLForType(rawURL string, nodeType string) error {
|
||||
rawURL = strings.TrimSpace(rawURL)
|
||||
nodeType = strings.ToLower(strings.TrimSpace(nodeType))
|
||||
parsed, err := url.Parse(rawURL)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return constant.RpcNodeURLErr
|
||||
}
|
||||
scheme := strings.ToLower(parsed.Scheme)
|
||||
switch nodeType {
|
||||
case mdb.RpcNodeTypeHttp:
|
||||
if scheme == "http" || scheme == "https" {
|
||||
return nil
|
||||
}
|
||||
return constant.RpcNodeHTTPURLErr
|
||||
case mdb.RpcNodeTypeWs:
|
||||
if scheme == "ws" || scheme == "wss" {
|
||||
return nil
|
||||
}
|
||||
return constant.RpcNodeWebSocketURLErr
|
||||
default:
|
||||
return constant.RpcNodeTypeErr
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user