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  }