From b36d52a90cab58ec40940f491af870d10f461bf7 Mon Sep 17 00:00:00 2001 From: pk910 Date: Tue, 17 Jun 2025 18:53:53 +0200 Subject: [PATCH] allow multiple client groups --- spamoor/client.go | 99 +++++++++++++---- spamoor/clientpool.go | 11 +- webui/handlers/api/api.go | 36 ++++-- webui/handlers/clients.go | 16 +-- webui/templates/clients/clients.html | 160 +++++++++++++++++++++------ 5 files changed, 246 insertions(+), 76 deletions(-) diff --git a/spamoor/client.go b/spamoor/client.go index 72d0855..b28fdd4 100644 --- a/spamoor/client.go +++ b/spamoor/client.go @@ -26,8 +26,8 @@ type Client struct { rpcClient *rpc.Client logger *logrus.Entry - clientGroup string - enabled bool + clientGroups []string + enabled bool gasSuggestionMutex sync.Mutex lastGasSuggestion time.Time @@ -45,12 +45,14 @@ type Client struct { // NewClient creates a new Client instance with the specified RPC host URL. // The rpchost parameter supports special prefixes: // - headers(key:value|key2:value2) - sets custom HTTP headers -// - group(name) - assigns the client to a named group +// - group(name) - assigns the client to a named group (can be used multiple times) +// - group(name1,name2,name3) - assigns the client to multiple groups (comma-separated) // -// Example: "headers(Authorization:Bearer token|User-Agent:MyApp)group(mainnet)http://localhost:8545" +// Example: "headers(Authorization:Bearer token|User-Agent:MyApp)group(mainnet)group(primary)http://localhost:8545" +// Example: "group(mainnet,primary,backup)http://localhost:8545" func NewClient(rpchost string) (*Client, error) { headers := map[string]string{} - clientGroup := "default" + clientGroups := []string{"default"} for { if strings.HasPrefix(rpchost, "headers(") { @@ -67,7 +69,24 @@ func NewClient(rpchost string) (*Client, error) { groupEnd := strings.Index(rpchost, ")") groupStr := rpchost[6:groupEnd] rpchost = rpchost[groupEnd+1:] - clientGroup = groupStr + + // Parse comma-separated groups + for _, group := range strings.Split(groupStr, ",") { + group = strings.TrimSpace(group) + if group != "" { + // Check if group already exists to avoid duplicates + exists := false + for _, existing := range clientGroups { + if existing == group { + exists = true + break + } + } + if !exists { + clientGroups = append(clientGroups, group) + } + } + } } else { break } @@ -84,12 +103,12 @@ func NewClient(rpchost string) (*Client, error) { } return &Client{ - client: ethclient.NewClient(rpcClient), - rpcClient: rpcClient, - rpchost: rpchost, - logger: logrus.WithField("rpc", rpchost), - clientGroup: clientGroup, - enabled: true, + client: ethclient.NewClient(rpcClient), + rpcClient: rpcClient, + rpchost: rpchost, + logger: logrus.WithField("rpc", rpchost), + clientGroups: clientGroups, + enabled: true, }, nil } @@ -101,10 +120,34 @@ func (client *Client) GetName() string { return name } -// GetClientGroup returns the client group name assigned during initialization. -// Defaults to "default" if no group was specified. +// GetClientGroup returns the first client group name assigned during initialization. +// Defaults to "default" if no group was specified. For multiple groups, use GetClientGroups(). func (client *Client) GetClientGroup() string { - return client.clientGroup + if len(client.clientGroups) > 0 { + return client.clientGroups[0] + } + return "default" +} + +// GetClientGroups returns all client group names assigned to this client. +func (client *Client) GetClientGroups() []string { + if len(client.clientGroups) == 0 { + return []string{"default"} + } + return client.clientGroups +} + +// HasGroup checks if the client belongs to the specified group. +func (client *Client) HasGroup(group string) bool { + if group == "" { + group = "default" + } + for _, clientGroup := range client.clientGroups { + if clientGroup == group { + return true + } + } + return false } // GetEthClient returns the underlying go-ethereum ethclient.Client instance. @@ -143,10 +186,28 @@ func (client *Client) UpdateWallet(ctx context.Context, wallet *Wallet) error { return nil } -// SetClientGroup sets the client group name for the client. -// This is used to group clients together and target them with specific scenarios. -func (client *Client) SetClientGroup(group string) { - client.clientGroup = group +// SetClientGroups sets multiple client group names for the client, replacing all existing groups. +func (client *Client) SetClientGroups(groups []string) { + // Remove duplicates and empty strings + uniqueGroups := []string{} + for _, group := range groups { + group = strings.TrimSpace(group) + if group == "" { + continue + } + exists := false + for _, existing := range uniqueGroups { + if existing == group { + exists = true + break + } + } + if !exists { + uniqueGroups = append(uniqueGroups, group) + } + } + + client.clientGroups = uniqueGroups } // IsEnabled returns whether the client is enabled for selection. diff --git a/spamoor/clientpool.go b/spamoor/clientpool.go index ec150fa..086c6ec 100644 --- a/spamoor/clientpool.go +++ b/spamoor/clientpool.go @@ -182,22 +182,23 @@ func (pool *ClientPool) GetClient(mode ClientSelectionMode, input int, group str clientCandidates := make([]*Client, 0) if group == "" { + // Empty group means default group for _, client := range pool.goodClients { - if client.IsEnabled() && client.GetClientGroup() == "default" { + if client.IsEnabled() && client.HasGroup("default") { clientCandidates = append(clientCandidates, client) } } } else if group == "*" { + // Wildcard means any group for _, client := range pool.goodClients { if client.IsEnabled() { clientCandidates = append(clientCandidates, client) } } - } - - if len(clientCandidates) == 0 { + } else { + // Specific group name for _, client := range pool.goodClients { - if client.IsEnabled() && (group == "" || client.GetClientGroup() == group) { + if client.IsEnabled() && client.HasGroup(group) { clientCandidates = append(clientCandidates, client) } } diff --git a/webui/handlers/api/api.go b/webui/handlers/api/api.go index 3c1aec9..6f8d750 100644 --- a/webui/handlers/api/api.go +++ b/webui/handlers/api/api.go @@ -545,19 +545,21 @@ func (ah *APIHandler) StreamSpammerLogs(w http.ResponseWriter, r *http.Request) // ClientEntry represents a client in the API response type ClientEntry struct { - Index int `json:"index"` - Name string `json:"name"` - Group string `json:"group"` - Version string `json:"version"` - BlockHeight uint64 `json:"block_height"` - IsReady bool `json:"ready"` - RpcHost string `json:"rpc_host"` - Enabled bool `json:"enabled"` + Index int `json:"index"` + Name string `json:"name"` + Group string `json:"group"` // First group for backward compatibility + Groups []string `json:"groups"` // All groups + Version string `json:"version"` + BlockHeight uint64 `json:"block_height"` + IsReady bool `json:"ready"` + RpcHost string `json:"rpc_host"` + Enabled bool `json:"enabled"` } // UpdateClientGroupRequest represents the request body for updating a client group type UpdateClientGroupRequest struct { - Group string `json:"group"` + Group string `json:"group,omitempty"` // Single group for backward compatibility + Groups []string `json:"groups,omitempty"` // Multiple groups } // UpdateClientEnabledRequest represents the request body for updating a client's enabled state @@ -592,6 +594,7 @@ func (ah *APIHandler) GetClients(w http.ResponseWriter, r *http.Request) { Index: i, Name: client.GetName(), Group: client.GetClientGroup(), + Groups: client.GetClientGroups(), Version: version, BlockHeight: blockHeight, IsReady: slices.Contains(goodClients, client), @@ -608,10 +611,10 @@ func (ah *APIHandler) GetClients(w http.ResponseWriter, r *http.Request) { // @Id updateClientGroup // @Summary Update client group // @Tags Client -// @Description Updates the group for a specific client +// @Description Updates the group(s) for a specific client. Supports both single group (backward compatibility) and multiple groups. // @Accept json // @Param index path int true "Client index" -// @Param request body UpdateClientGroupRequest true "New group name" +// @Param request body UpdateClientGroupRequest true "New group name(s)" // @Success 200 {object} Response "Success" // @Failure 400 {object} Response "Invalid client index" // @Failure 404 {object} Response "Client not found" @@ -637,7 +640,16 @@ func (ah *APIHandler) UpdateClientGroup(w http.ResponseWriter, r *http.Request) } client := allClients[index] - client.SetClientGroup(req.Group) + + // Handle both single group (backward compatibility) and multiple groups + if len(req.Groups) > 0 { + client.SetClientGroups(req.Groups) + } else if req.Group != "" { + client.SetClientGroups([]string{req.Group}) + } else { + http.Error(w, "Either 'group' or 'groups' must be provided", http.StatusBadRequest) + return + } w.WriteHeader(http.StatusOK) } diff --git a/webui/handlers/clients.go b/webui/handlers/clients.go index a13fe35..945bb5a 100644 --- a/webui/handlers/clients.go +++ b/webui/handlers/clients.go @@ -16,13 +16,14 @@ type ClientsPage struct { } type ClientsPageClient struct { - Index int `json:"index"` - Name string `json:"name"` - Group string `json:"group"` - Version string `json:"version"` - BlockHeight uint64 `json:"block_height"` - IsReady bool `json:"ready"` - Enabled bool `json:"enabled"` + Index int `json:"index"` + Name string `json:"name"` + Group string `json:"group"` // First group for backward compatibility + Groups []string `json:"groups"` // All groups + Version string `json:"version"` + BlockHeight uint64 `json:"block_height"` + IsReady bool `json:"ready"` + Enabled bool `json:"enabled"` } // Clients will return the "clients" page using a go template @@ -66,6 +67,7 @@ func (fh *FrontendHandler) getClientsPageData(ctx context.Context) (*ClientsPage Index: idx, Name: client.GetName(), Group: client.GetClientGroup(), + Groups: client.GetClientGroups(), BlockHeight: blockHeight, IsReady: slices.Contains(goodClients, client), Enabled: client.IsEnabled(), diff --git a/webui/templates/clients/clients.html b/webui/templates/clients/clients.html index 653c0e2..758814c 100644 --- a/webui/templates/clients/clients.html +++ b/webui/templates/clients/clients.html @@ -9,7 +9,7 @@

Clients

# Name - Group + Groups Status Enabled Block Height @@ -23,7 +23,11 @@

Clients

{{ $client.Index }} {{ $client.Name }} - {{ $client.Group }} +
+ {{ range $client.Groups }} + {{ . }} + {{ end }} +
{{ if $client.IsReady }} @@ -46,10 +50,10 @@

Clients

-
- -