github.com/lbryio/lbcd@v0.22.119/rpcclient/examples/lbcdblocknotify/bridge.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "log" 8 "net" 9 "os" 10 "os/exec" 11 "strings" 12 "sync" 13 "syscall" 14 "time" 15 ) 16 17 type bridge struct { 18 ctx context.Context 19 20 prevJobContext context.Context 21 prevJobCancel context.CancelFunc 22 23 eventCh chan interface{} 24 errorc chan error 25 wg sync.WaitGroup 26 27 stratum *stratumClient 28 29 customCmd string 30 } 31 32 func newBridge(stratumServer, stratumPass, coinid string) *bridge { 33 34 s := &bridge{ 35 ctx: context.Background(), 36 eventCh: make(chan interface{}), 37 errorc: make(chan error), 38 } 39 40 if len(stratumServer) > 0 { 41 s.stratum = newStratumClient(stratumServer, stratumPass, coinid) 42 } 43 44 return s 45 } 46 47 func (b *bridge) start() { 48 49 if b.stratum != nil { 50 backoff := time.Second 51 for { 52 err := b.stratum.dial() 53 if err == nil { 54 break 55 } 56 log.Printf("WARN: stratum.dial() error: %s, retry in %s", err, backoff) 57 time.Sleep(backoff) 58 if backoff < 60*time.Second { 59 backoff += time.Second 60 } 61 } 62 } 63 64 for e := range b.eventCh { 65 switch e := e.(type) { 66 case *eventBlockConected: 67 b.handleFilteredBlockConnected(e) 68 default: 69 b.errorc <- fmt.Errorf("unknown event type: %T", e) 70 return 71 } 72 } 73 } 74 75 func (b *bridge) handleFilteredBlockConnected(e *eventBlockConected) { 76 77 if !*quiet { 78 log.Printf("Block connected: %s (%d) %v", e.header.BlockHash(), e.height, e.header.Timestamp) 79 } 80 81 hash := e.header.BlockHash().String() 82 height := e.height 83 84 // Cancel jobs on previous block. It's safe if they are already done. 85 if b.prevJobContext != nil { 86 select { 87 case <-b.prevJobContext.Done(): 88 log.Printf("prev one canceled") 89 default: 90 b.prevJobCancel() 91 } 92 } 93 94 // Wait until all previous jobs are done or canceled. 95 b.wg.Wait() 96 97 // Create and save cancelable subcontext for new jobs. 98 ctx, cancel := context.WithCancel(b.ctx) 99 b.prevJobContext, b.prevJobCancel = ctx, cancel 100 101 if len(b.customCmd) > 0 { 102 go b.execCustomCommand(ctx, hash, height) 103 } 104 105 // Send stratum update block message 106 if b.stratum != nil { 107 go b.stratumUpdateBlock(ctx, hash, height) 108 } 109 } 110 111 func (s *bridge) stratumUpdateBlock(ctx context.Context, hash string, height int32) { 112 s.wg.Add(1) 113 defer s.wg.Done() 114 115 backoff := time.Second 116 retry := func(err error) { 117 if backoff < 60*time.Second { 118 backoff += time.Second 119 } 120 log.Printf("WARN: stratum.send() on block %d error: %s", height, err) 121 time.Sleep(backoff) 122 s.stratum.dial() 123 } 124 125 msg := stratumUpdateBlockMsg(*stratumPass, *coinid, hash) 126 127 for { 128 switch err := s.stratum.send(ctx, msg); { 129 case err == nil: 130 return 131 case errors.Is(err, context.Canceled): 132 log.Printf("INFO: stratum.send() on block %d: %s.", height, err) 133 return 134 case errors.Is(err, syscall.EPIPE): 135 errClose := s.stratum.conn.Close() 136 if errClose != nil { 137 log.Printf("WARN: stratum.conn.Close() on block %d: %s.", height, errClose) 138 } 139 retry(err) 140 case errors.Is(err, net.ErrClosed): 141 retry(err) 142 default: 143 retry(err) 144 } 145 } 146 147 } 148 149 func (s *bridge) execCustomCommand(ctx context.Context, hash string, height int32) { 150 s.wg.Add(1) 151 defer s.wg.Done() 152 153 cmd := strings.ReplaceAll(s.customCmd, "%s", hash) 154 err := doExecCustomCommand(ctx, cmd) 155 if err != nil { 156 log.Printf("ERROR: execCustomCommand on block %s(%d): %s", hash, height, err) 157 } 158 } 159 160 func doExecCustomCommand(ctx context.Context, cmd string) error { 161 strs := strings.Split(cmd, " ") 162 path, err := exec.LookPath(strs[0]) 163 if errors.Is(err, exec.ErrDot) { 164 err = nil 165 } 166 if err != nil { 167 return err 168 } 169 c := exec.CommandContext(ctx, path, strs[1:]...) 170 c.Stdout = os.Stdout 171 return c.Run() 172 }