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 }