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  }