github.com/0xsequence/ethkit@v1.25.0/ethmempool/ethmempool.go (about)

     1  package ethmempool
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  	"sync/atomic"
     9  
    10  	"github.com/0xsequence/ethkit/go-ethereum/rpc"
    11  	"github.com/goware/logger"
    12  )
    13  
    14  type Options struct {
    15  	Logger logger.Logger
    16  }
    17  
    18  type Mempool struct {
    19  	options Options
    20  
    21  	log              logger.Logger
    22  	nodeWebsocketURL string
    23  	client           *rpc.Client
    24  	subscribers      []*subscriber
    25  
    26  	ctx     context.Context
    27  	ctxStop context.CancelFunc
    28  	running int32
    29  	mu      sync.RWMutex
    30  }
    31  
    32  func NewMempool(nodeWebsocketURL string, opts ...Options) (*Mempool, error) {
    33  	options := Options{}
    34  	if len(opts) > 0 {
    35  		options = opts[0]
    36  	}
    37  
    38  	return &Mempool{
    39  		options:          options,
    40  		nodeWebsocketURL: nodeWebsocketURL,
    41  		subscribers:      make([]*subscriber, 0),
    42  	}, nil
    43  }
    44  
    45  func (m *Mempool) Run(ctx context.Context) error {
    46  	if m.IsRunning() {
    47  		return fmt.Errorf("ethmempool: already running")
    48  	}
    49  
    50  	m.ctx, m.ctxStop = context.WithCancel(ctx)
    51  
    52  	atomic.StoreInt32(&m.running, 1)
    53  	defer atomic.StoreInt32(&m.running, 0)
    54  
    55  	// open websocket connection
    56  	var err error
    57  	m.client, err = rpc.Dial(m.nodeWebsocketURL)
    58  	if err != nil {
    59  		return fmt.Errorf("ethmempool: failed to open websocket connection to %s: %w", m.nodeWebsocketURL, err)
    60  	}
    61  
    62  	// stream events and broadcast to subscribers
    63  	return m.stream()
    64  }
    65  
    66  func (m *Mempool) Stop() {
    67  	m.ctxStop()
    68  
    69  	// disconnect websocket
    70  	m.client.Close()
    71  }
    72  
    73  func (m *Mempool) IsRunning() bool {
    74  	return atomic.LoadInt32(&m.running) == 1
    75  }
    76  
    77  func (m *Mempool) Options() Options {
    78  	return m.options
    79  }
    80  
    81  func (m *Mempool) Subscribe() Subscription {
    82  	return m.subscribe(nil)
    83  }
    84  
    85  func (m *Mempool) SubscribeWithFilter(notifyFilterFunc NotifyFilterFunc) Subscription {
    86  	return m.subscribe(notifyFilterFunc)
    87  }
    88  
    89  func (m *Mempool) subscribe(notifyFilterFunc NotifyFilterFunc) Subscription {
    90  	m.mu.Lock()
    91  	defer m.mu.Unlock()
    92  
    93  	subscriber := &subscriber{
    94  		ch:               make(chan string, 1024),
    95  		done:             make(chan struct{}),
    96  		notifyFilterFunc: notifyFilterFunc, // optional, can be nil
    97  	}
    98  
    99  	subscriber.unsubscribe = func() {
   100  		m.mu.Lock()
   101  		defer m.mu.Unlock()
   102  		for i, sub := range m.subscribers {
   103  			if sub == subscriber {
   104  				m.subscribers = append(m.subscribers[:i], m.subscribers[i+1:]...)
   105  				close(subscriber.done)
   106  				close(subscriber.ch)
   107  				return
   108  			}
   109  		}
   110  	}
   111  
   112  	m.subscribers = append(m.subscribers, subscriber)
   113  
   114  	return subscriber
   115  }
   116  
   117  func (m *Mempool) stream() error {
   118  	ch := make(chan string) // txn hash strings
   119  
   120  	sub, err := m.client.EthSubscribe(m.ctx, ch, "newPendingTransactions")
   121  	if err != nil {
   122  		return fmt.Errorf("ethmempool: stream failed to subscribe %w", err)
   123  	}
   124  	defer sub.Unsubscribe()
   125  
   126  	for {
   127  		select {
   128  
   129  		case <-m.ctx.Done():
   130  			return nil
   131  
   132  		case <-sub.Err():
   133  			return fmt.Errorf("ethmempool: websocket error %w", err)
   134  
   135  		case pendingTxnHash := <-ch:
   136  			// notify all subscribers..
   137  			pendingTxnHash = strings.ToLower(pendingTxnHash)
   138  			for _, sub := range m.subscribers {
   139  				if sub.notifyFilterFunc == nil || sub.notifyFilterFunc(pendingTxnHash) {
   140  					sub.ch <- pendingTxnHash
   141  				}
   142  			}
   143  		}
   144  	}
   145  }