clientmanager

package module
v0.0.0-...-4d8cdc4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2025 License: MIT Imports: 13 Imported by: 1

README

Iwinswap ETH Client Manager

A robust, production-ready Go package for managing connections to multiple Ethereum nodes. It provides health monitoring, latency tracking, and intelligent client selection to ensure high availability and performance for decentralized applications.


Overview

Interacting with Ethereum nodes can be unreliable—nodes may fall out of sync, become unresponsive, or introduce latency. iwinswap-ethclient-manager abstracts away this complexity by wrapping one or more go-ethereum clients with a management layer that continuously monitors health and performance.

The package provides:

  • Client: A wrapper around a single ethclient.Client that adds health checks, latency tracking, concurrency limits, and graceful lifecycle management.
  • ClientManager: A high-level manager that maintains a pool of Client instances and selects the best one for your RPC calls based on health and latency.

Features

  • Automatic Health Monitoring Periodically checks the status of each node by fetching the latest block number.

  • Latency Tracking Records response time for each health check.

  • Intelligent Client Selection

    • GetClient(): Returns a healthy, synced client using round-robin rotation.
    • GetPreferredClient(): Selects a low-latency, healthy client from a preferred pool.
  • Concurrency Limiting Each client limits concurrent RPC calls for stability under load.

  • Graceful Shutdown Uses context.Context for safe termination of goroutines and connections.

  • Customizable Configuration Fine-tune polling intervals, timeouts, and concurrency per client.


Installation

go get github.com/Iwinswap/iwinswap-ethclient-manager

Usage

Basic Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/Iwinswap/iwinswap-ethclient-manager/clientmanager"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	endpoints := []string{
		"https://eth.public-node.com",
		"https://ethereum.publicnode.com",
		"https://rpc.ankr.com/eth",
	}

	managerConfig := &clientmanager.ClientManagerConfig{
		ClientConfig: &clientmanager.ClientConfig{},
	}

	manager, err := clientmanager.NewClientManager(ctx, endpoints, managerConfig)
	if err != nil {
		log.Fatalf("Failed to create client manager: %v", err)
	}
	defer manager.Close()

	log.Println("Waiting for clients to become healthy...")
	time.Sleep(6 * time.Second)

	client, err := manager.GetClient()
	if err != nil {
		log.Fatalf("Failed to get a healthy client: %v", err)
	}

	blockNumber, err := client.BlockNumber(context.Background())
	if err != nil {
		log.Fatalf("Failed to get latest block number: %v", err)
	}
	fmt.Printf("Latest block number: %d\n", blockNumber)

	preferredClient, err := manager.GetPreferredClient()
	if err != nil {
		log.Fatalf("Failed to get a preferred client: %v", err)
	}

	chainID, err := preferredClient.ChainID(context.Background())
	if err != nil {
		log.Fatalf("Failed to get chain ID: %v", err)
	}
	fmt.Printf("Chain ID: %s\n", chainID.String())
}

Advanced Configuration
clientConfig := &clientmanager.ClientConfig{
	MonitorHealthInterval:   250 * time.Millisecond,
	HealthCheckRPCTimeout:   5 * time.Second,
	WaitHealthyPollInterval: 500 * time.Millisecond,
	MaxConcurrentETHCalls:   32,
}

managerConfig := &clientmanager.ClientManagerConfig{
	ClientConfig: clientConfig,
	PreferredClientMaxLatencyMultiplier: 3,
}

Configuration Reference

ClientConfig (Per-Client Settings)
Field Type Default Description
MonitorHealthInterval time.Duration 15s How often to check node health.
HealthCheckRPCTimeout time.Duration 5s Timeout for a single health check RPC call.
WaitHealthyPollInterval time.Duration 1s How frequently to poll for health in wait loops.
MaxConcurrentETHCalls int 10 Maximum simultaneous RPC calls to this client.
Logger Logger default Optional logger for client output.
ClientManagerConfig (Manager-Level Settings)
Field Type Default Description
ClientConfig *ClientConfig nil Configuration applied to all managed clients.
PreferredClientMaxLatencyMultiplier int 3 Multiplier for preferred client selection. Any healthy client with latency ≤ minLatency * multiplier is eligible.
Logger Logger default Optional logger for manager-level events and errors.

Contributing

We welcome contributions! To get started:

  1. Fork the repository.
  2. Create a new branch (git checkout -b feature/your-feature).
  3. Commit your changes (git commit -m 'Add new feature').
  4. Push to your fork (git push origin feature/your-feature).
  5. Open a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	ETHClient *ethclients.ETHClientWithMaxConcurrentCalls
	// contains filtered or unexported fields
}

func NewClientFromDial

func NewClientFromDial(
	ctx context.Context,
	dial string,
	config *ClientConfig,
) (*Client, error)

func NewClientFromETHClient

func NewClientFromETHClient(
	parentContext context.Context,
	ethNodeClient ethclients.ETHClient,
	config *ClientConfig,
) (*Client, error)

NewClientFromETHClient creates a new Client with the given underlying ETHClient and applies the provided configuration.

func (*Client) Close

func (c *Client) Close()

Close gracefully stops health monitoring and closes the client connection.

func (*Client) Healthy

func (c *Client) Healthy() bool

Healthy returns the client's health status.

func (*Client) Latency

func (c *Client) Latency() float64

func (*Client) LatestBlockNumber

func (c *Client) LatestBlockNumber() uint64

LatestBlockNumber safely returns the client's latest block number

func (*Client) WaitUntilHealthy

func (c *Client) WaitUntilHealthy(ctx context.Context, timeout time.Duration) error

WaitUntilHealthy waits until the client is healthy or a timeout occurs.

type ClientConfig

type ClientConfig struct {
	MonitorHealthInterval   time.Duration // How often to perform health checks.
	HealthCheckRPCTimeout   time.Duration // Timeout for the RPC call within each health check.
	WaitHealthyPollInterval time.Duration // How often to poll for health in WaitUntilHealthy.
	MaxConcurrentETHCalls   int           // Maximum concurrent calls allowed to the underlying ETHClient.
	DialTimeout             time.Duration // timeout for dial client
	Logger                  Logger
}

ClientConfig holds configuration parameters for the Client.

type ClientManager

type ClientManager struct {
	// contains filtered or unexported fields
}

func NewClientManager

func NewClientManager(
	ctx context.Context,
	endpoints []string,
	config *ClientManagerConfig,
) (*ClientManager, error)

NewClientManager creates a new ClientManager, dialing each provided endpoint.

func NewClientManagerFromClients

func NewClientManagerFromClients(
	clients []*Client,
	config *ClientManagerConfig,
) (*ClientManager, error)

func (*ClientManager) Close

func (cm *ClientManager) Close()

Close gracefully cancels all subscriptions and closes all clients.

func (*ClientManager) GetClient

func (cm *ClientManager) GetClient() (ethclients.ETHClient, error)

GetClient returns an Ethereum client, preferring one that is healthy and up-to-date.

func (*ClientManager) GetClients

func (cm *ClientManager) GetClients() []ethclients.ETHClient

GetClients returns all Ethereum clients.

func (*ClientManager) GetPreferredClient

func (cm *ClientManager) GetPreferredClient() (ethclients.ETHClient, error)

problem - client manager might have to manage multiple clients in different geographical locations users might need `preferred` clients, clients faster than the average how do we select these clients? we set a cutoff and we pick randomly

type ClientManagerConfig

type ClientManagerConfig struct {
	ClientConfig                        *ClientConfig
	PreferredClientMaxLatencyMultiplier int
	Logger                              Logger
}

type DefaultLogger

type DefaultLogger struct {
}

func (*DefaultLogger) Debug

func (logger *DefaultLogger) Debug(msg string, args ...any)

func (*DefaultLogger) Error

func (logger *DefaultLogger) Error(msg string, args ...any)

func (*DefaultLogger) Info

func (logger *DefaultLogger) Info(msg string, args ...any)

func (*DefaultLogger) Warn

func (logger *DefaultLogger) Warn(msg string, args ...any)

type Logger

type Logger interface {
	Info(msg string, args ...any)
	Error(msg string, args ...any)
	Warn(msg string, args ...any)
	Debug(msg string, args ...any)
}

type NewBlockNotification

type NewBlockNotification struct {
	Block *types.Block
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL