subscriber

package module
v0.0.0-...-38edc61 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2025 License: MIT Imports: 9 Imported by: 1

README

Iwinswap ETH Block Subscriber

A resilient, concurrent, multi-client Ethereum block subscriber for Go applications. This package manages subscriptions to multiple Ethereum nodes, handles failures gracefully, and provides a single, unified channel of the latest blocks.


Overview

The subscriber package ensures reliable access to the Ethereum block stream, even when connected to multiple unreliable RPC nodes. It abstracts away the challenges of:

  • Subscription management
  • Handling disconnects or RPC failures
  • Synchronizing across clients

Features

  • Multi-Client Management: Connects to and manages multiple Ethereum clients.
  • Unified Block Stream: Publishes the latest unique block to a single channel: (<-chan *types.Block).
  • Self-Healing: Automatically reconnects dropped clients during the next refresh.
  • Resilience: Gracefully recovers from RPC or subscription errors.
  • Configurable: Tune intervals, buffer sizes, and timeouts.
  • Pluggable Logger: Use your preferred logging framework via a minimal Logger interface.
  • Graceful Shutdown: Ensures safe termination of background processes.

Installation

go get github.com/Iwinswap/iwinswap-eth-block-subscriber/subscriber

Example Usage

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	subscriber "github.com/Iwinswap/iwinswap-eth-block-subscriber/subscriber"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
)

type AppLogger struct{}

func (l *AppLogger) Debug(msg string, args ...any) { log.Printf("[DEBUG] "+msg, args...) }
func (l *AppLogger) Info(msg string, args ...any)  { log.Printf("[INFO] "+msg, args...) }
func (l *AppLogger) Warn(msg string, args ...any)  { log.Printf("[WARN] "+msg, args...) }
func (l *AppLogger) Error(msg string, args ...any) { log.Printf("[ERROR] "+msg, args...) }

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

	shutdown := make(chan os.Signal, 1)
	signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)

	getHealthyClients := func() []subscriber.ETHClient {
		client1, err := ethclient.DialContext(ctx, "wss://your-mainnet-rpc-url-1")
		if err != nil {
			log.Printf("Failed to connect to client1: %v", err)
		}
		client2, err := ethclient.DialContext(ctx, "wss://your-mainnet-rpc-url-2")
		if err != nil {
			log.Printf("Failed to connect to client2: %v", err)
		}

		var clients []subscriber.ETHClient
		if client1 != nil {
			clients = append(clients, client1)
		}
		if client2 != nil {
			clients = append(clients, client2)
		}
		return clients
	}

	config := &subscriber.SubscriberConfig{
		UpdateClientSetInterval: 1 * time.Minute,
		BlockByNumberTimeout:    15 * time.Second,
		NewBlockBuffer:          100,
		Logger:                  &AppLogger{},
	}

	blockC := make(chan *types.Block, 100)
	s := subscriber.NewBlockSubscriber(ctx, blockC, getHealthyClients, config)
	log.Println("Block subscriber started.")

	for {
		select {
		case block := <-blockC:
			fmt.Printf("New block: #%d [%s]\n", block.NumberU64(), block.Hash().Hex())
		case <-shutdown:
			log.Println("Shutting down...")
			s.Close()
			return
		case <-ctx.Done():
			log.Println("Context canceled. Exiting...")
			s.Close()
			return
		}
	}
}

Configuration

Field Description Default
UpdateClientSetInterval How often to refresh the list of healthy clients 1m
BlockByNumberTimeout Timeout for fetching the full block via RPC 10s
NewBlockBuffer Channel buffer size for incoming blocks 100
Logger Custom logger implementing Logger interface Stdout logger

Logger and Client Interfaces

The subscriber defines minimal interfaces for flexibility.

// The ETHClient interface defines the methods the subscriber needs.
// The standard `ethclient.Client` from go-ethereum satisfies this automatically.
type ETHClient interface {
	SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error)
	BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
}

// The Logger interface allows for pluggable logging frameworks.
type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

To use a structured logger like Zap or Logrus, simply wrap it to satisfy the interface.


How It Works

Block Processing
  • Subscribes to new block headers across all clients.
  • Fetches the full block from the client that sent the header.
  • Delivers blocks with increasing numbers (i.e., highest number wins).
  • Does not handle reorgs — this is delegated to the consumer.
Client Management
  • Periodically invokes getHealthyClients.
  • Detects new clients and adds them to the subscription pool.
  • Detects failed subscriptions and drops the client.
  • Re-attempts failed clients in future refresh cycles if still considered healthy.

Contributing

Bug reports and pull requests are welcome at: https://github.com/Iwinswap/iwinswap-eth-block-subscriber


License

MIT License

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BlockSubscriber

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

func NewBlockSubscriber

func NewBlockSubscriber(
	parentContext context.Context,
	newBlockReceiverC chan *types.Block,
	getHealthyClients func() []ETHClient,
	config *SubscriberConfig,
) *BlockSubscriber

func (*BlockSubscriber) Close

func (bs *BlockSubscriber) Close()

func (*BlockSubscriber) LatestBlock

func (bs *BlockSubscriber) LatestBlock() *types.Block

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 ETHClient

type ETHClient interface {
	SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error)
	BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
}

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 SubscriberConfig

type SubscriberConfig struct {
	UpdateClientSetInterval time.Duration
	BlockByNumberTimeout    time.Duration
	NewBlockBuffer          int
	Logger                  Logger
}

Jump to

Keyboard shortcuts

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