8000 allow multiple client groups by pk910 · Pull Request #63 · ethpandaops/spamoor · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

allow multiple client groups #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 80 additions & 19 deletions spamoor/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(") {
Expand All @@ -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
}
Expand All @@ -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
}

Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 6 additions & 5 deletions spamoor/clientpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
36 changes: 24 additions & 12 deletions webui/handlers/api/api.go
DDB0
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -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"
Expand All @@ -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)
}
Expand Down
16 changes: 9 additions & 7 deletions webui/handlers/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down
Loading
0