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 }