github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/seqno.go (about) 1 package stellar 2 3 import ( 4 "sync" 5 6 "github.com/keybase/client/go/libkb" 7 "github.com/keybase/client/go/protocol/stellar1" 8 "github.com/stellar/go/xdr" 9 ) 10 11 // SeqnoProvider implements build.SequenceProvider. It can be 12 // used for several transactions in a row. 13 type SeqnoProvider struct { 14 mctx libkb.MetaContext 15 walletState *WalletState 16 refresh sync.Once 17 } 18 19 // NewSeqnoProvider creates a SeqnoProvider. It also returns an `unlock` function 20 // that must be called after the operation(s) that used this seqno provider have 21 // been submitted. 22 // 23 // The idea here is to fix a race where multiple calls to send payments will 24 // make a SeqnoProvider and while they will consume seqnos in order, they are 25 // not guaranteed to be submitted in order. In particular, the `dust storm` 26 // function in the bot has a tendency to expose the race. 27 func NewSeqnoProvider(mctx libkb.MetaContext, walletState *WalletState) (seqnoProvider *SeqnoProvider, unlock func()) { 28 walletState.SeqnoLock() 29 return &SeqnoProvider{ 30 mctx: mctx, 31 walletState: walletState, 32 }, walletState.SeqnoUnlock 33 } 34 35 // SequenceForAccount implements build.SequenceProvider. 36 func (s *SeqnoProvider) SequenceForAccount(aid string) (xdr.SequenceNumber, error) { 37 s.refresh.Do(func() { 38 err := s.walletState.ForceSeqnoRefresh(s.mctx, stellar1.AccountID(aid)) 39 if err != nil { 40 s.mctx.Debug("SeqnoProvider ws.ForceSeqnoRefresh error: %s", err) 41 } 42 }) 43 seqno, err := s.walletState.AccountSeqnoAndBump(s.mctx.Ctx(), stellar1.AccountID(aid)) 44 if err != nil { 45 s.mctx.Debug("SeqnoProvider AccountSeqnoAndBump error: %s", err) 46 return 0, err 47 } 48 49 s.mctx.Debug("SeqnoProvider.SequenceForAccount(%s) -> %d", aid, seqno) 50 return xdr.SequenceNumber(seqno), nil 51 } 52 53 // ClaimSeqnoProvider is a build.SequenceProvider for relay claims. It should only 54 // be used for relay claims. 55 // 56 // It only uses the network and skips any of the work in WalletState to keep track 57 // of in-use seqnos for multiple concurrent payments. 58 // 59 // It also returns an `unlock` function that must be called after the operation 60 // that used this seqno provider has been submitted. 61 type ClaimSeqnoProvider struct { 62 mctx libkb.MetaContext 63 walletState *WalletState 64 } 65 66 // NewClaimSeqnoProvider creates a ClaimSeqnoProvider for use in relay claims. 67 func NewClaimSeqnoProvider(mctx libkb.MetaContext, walletState *WalletState) (seqnoProvider *ClaimSeqnoProvider, unlock func()) { 68 walletState.SeqnoLock() 69 return &ClaimSeqnoProvider{ 70 mctx: mctx, 71 walletState: walletState, 72 }, walletState.SeqnoUnlock 73 } 74 75 // SequenceForAccount implements build.SequenceProvider. 76 func (s *ClaimSeqnoProvider) SequenceForAccount(aid string) (xdr.SequenceNumber, error) { 77 seqno, err := s.walletState.Remoter.AccountSeqno(s.mctx.Ctx(), stellar1.AccountID(aid)) 78 if err != nil { 79 return 0, err 80 } 81 return xdr.SequenceNumber(seqno), nil 82 }