github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/autoclaim.go (about)

     1  package stellar
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/keybase/client/go/gregor"
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/protocol/stellar1"
    11  )
    12  
    13  // Claims relay payments in the background.
    14  // Threadsafe.
    15  type AutoClaimRunner struct {
    16  	startOnce   sync.Once
    17  	shutdownCh  chan struct{}
    18  	kickCh      chan gregor.MsgID
    19  	walletState *WalletState
    20  }
    21  
    22  func NewAutoClaimRunner(walletState *WalletState) *AutoClaimRunner {
    23  	return &AutoClaimRunner{
    24  		shutdownCh:  make(chan struct{}, 1),
    25  		kickCh:      make(chan gregor.MsgID, 100),
    26  		walletState: walletState,
    27  	}
    28  }
    29  
    30  // Kick the processor into gear.
    31  // It will run until all relays in the queue are claimed.
    32  // And then dismiss the gregor message.
    33  // `trigger` is optional, and is of the gregor message that caused the kick.
    34  func (r *AutoClaimRunner) Kick(mctx libkb.MetaContext, trigger gregor.MsgID) {
    35  	mctx.Debug("AutoClaimRunner.Kick(trigger:%v)", trigger)
    36  	var onced bool
    37  	r.startOnce.Do(func() {
    38  		onced = true
    39  		go r.loop(libkb.NewMetaContextBackground(mctx.G()), trigger)
    40  	})
    41  	if !onced {
    42  		select {
    43  		case r.kickCh <- trigger:
    44  		default:
    45  		}
    46  	}
    47  }
    48  
    49  func (r *AutoClaimRunner) Shutdown(mctx libkb.MetaContext) {
    50  	mctx.Debug("AutoClaimRunner.Shutdown")
    51  	close(r.shutdownCh)
    52  }
    53  
    54  type autoClaimLoopAction string
    55  
    56  const (
    57  	autoClaimLoopActionFast      autoClaimLoopAction = "fast"
    58  	autoClaimLoopActionHibernate autoClaimLoopAction = "hibernate"
    59  	autoClaimLoopActionSnooze    autoClaimLoopAction = "snooze"
    60  )
    61  
    62  // `trigger` is optional
    63  func (r *AutoClaimRunner) loop(mctx libkb.MetaContext, trigger gregor.MsgID) {
    64  	var i int
    65  	for {
    66  		i++
    67  		mctx := mctx.WithLogTag("ACR") // shadow mctx for this round with a log tag
    68  		log := func(format string, args ...interface{}) {
    69  			mctx.Debug(fmt.Sprintf("AutoClaimRunnner round[%v] ", i) + fmt.Sprintf(format, args...))
    70  		}
    71  		action, err := r.step(mctx, i, trigger)
    72  		if err != nil {
    73  			log("error: %v", err)
    74  		}
    75  		log("action: %v", action)
    76  		switch action {
    77  		case autoClaimLoopActionFast:
    78  			// Go again
    79  		case autoClaimLoopActionHibernate:
    80  			// Wait for a kick
    81  			select {
    82  			case trigger = <-r.kickCh:
    83  			case <-r.shutdownCh:
    84  				return
    85  			}
    86  		case autoClaimLoopActionSnooze:
    87  			fallthrough
    88  		default:
    89  			// Pause for a few minutes
    90  			select {
    91  			case <-time.After(2 * time.Minute):
    92  			case trigger = <-r.kickCh:
    93  			case <-r.shutdownCh:
    94  				return
    95  			}
    96  		}
    97  	}
    98  }
    99  
   100  // `trigger` is optional
   101  func (r *AutoClaimRunner) step(mctx libkb.MetaContext, i int, trigger gregor.MsgID) (action autoClaimLoopAction, err error) {
   102  	log := func(format string, args ...interface{}) {
   103  		mctx.Debug(fmt.Sprintf("AutoClaimRunnner round[%v] ", i) + fmt.Sprintf(format, args...))
   104  	}
   105  	log("step begin")
   106  	token, err := r.walletState.AcquireAutoClaimLock(mctx.Ctx())
   107  	if err != nil {
   108  		return autoClaimLoopActionSnooze, err
   109  	}
   110  	if len(token) == 0 {
   111  		log("autoclaim lock is busy")
   112  		return autoClaimLoopActionSnooze, nil
   113  	}
   114  	defer func() {
   115  		rerr := r.walletState.ReleaseAutoClaimLock(mctx.Ctx(), token)
   116  		if rerr != nil {
   117  			log("error releasing autoclaim lock: %v", rerr)
   118  		}
   119  	}()
   120  	ac, err := r.walletState.NextAutoClaim(mctx.Ctx())
   121  	if err != nil {
   122  		return autoClaimLoopActionSnooze, err
   123  	}
   124  	if ac == nil {
   125  		log("no more autoclaims")
   126  		if trigger.String() != "" {
   127  			log("dismissing kick: %v", trigger)
   128  			err = mctx.G().GregorState.DismissItem(mctx.Ctx(), nil, trigger)
   129  			if err != nil {
   130  				log("error dismissing gregor kick: %v", err)
   131  				return autoClaimLoopActionHibernate, err
   132  			}
   133  			log("successfully dismissed kick")
   134  		}
   135  		return autoClaimLoopActionHibernate, nil
   136  	}
   137  	log("got next autoclaim: %v", ac.KbTxID)
   138  	err = r.claim(mctx, ac.KbTxID, token)
   139  	if err != nil {
   140  		return autoClaimLoopActionSnooze, err
   141  	}
   142  	log("successfully claimed: %v", ac.KbTxID)
   143  	return autoClaimLoopActionFast, nil
   144  }
   145  
   146  func (r *AutoClaimRunner) claim(mctx libkb.MetaContext, kbTxID stellar1.KeybaseTransactionID, token string) (err error) {
   147  	CreateWalletSoft(mctx)
   148  	into, err := GetOwnPrimaryAccountID(mctx)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	// Explicitly CLAIM. We don't want to accidentally auto YANK.
   153  	dir := stellar1.RelayDirection_CLAIM
   154  	// Use the user's autoclaim lock that we acquired.
   155  	_, err = Claim(mctx, r.walletState, kbTxID.String(), into, &dir, &token)
   156  	return err
   157  }