rpc: acquire global coin select lock in related RPCs

This ensures proper coin selection synchronization between lnd and users
of the RPC to avoid potential double spend errors.
This commit is contained in:
Wilmer Paulino 2020-06-09 19:37:17 -07:00
parent 9d9e54f83e
commit c2f1fe26c1
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
4 changed files with 44 additions and 4 deletions

View File

@ -38,6 +38,13 @@ type Config struct {
// any relevant requests to.
Wallet lnwallet.WalletController
// CoinSelectionLocker allows the caller to perform an operation, which
// is synchronized with all coin selection attempts. This can be used
// when an operation requires that all coin selection operations cease
// forward progress. Think of this as an exclusive lock on coin
// selection operations.
CoinSelectionLocker sweep.CoinSelectionLocker
// KeyRing is an interface that the WalletKit will use to derive any
// keys due to incoming client requests.
KeyRing keychain.KeyRing

View File

@ -10,6 +10,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
@ -258,7 +259,15 @@ func (w *WalletKit) ListUnspent(ctx context.Context,
// With our arguments validated, we'll query the internal wallet for
// the set of UTXOs that match our query.
utxos, err := w.cfg.Wallet.ListUnspentWitness(minConfs, maxConfs)
//
// We'll acquire the global coin selection lock to ensure there aren't
// any other concurrent processes attempting to lock any UTXOs which may
// be shown available to us.
var utxos []*lnwallet.Utxo
err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
utxos, err = w.cfg.Wallet.ListUnspentWitness(minConfs, maxConfs)
return err
})
if err != nil {
return nil, err
}
@ -301,7 +310,13 @@ func (w *WalletKit) LeaseOutput(ctx context.Context,
return nil, err
}
expiration, err := w.cfg.Wallet.LeaseOutput(lockID, *op)
// Acquire the global coin selection lock to ensure there aren't any
// other concurrent processes attempting to lease the same UTXO.
var expiration time.Time
err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
expiration, err = w.cfg.Wallet.LeaseOutput(lockID, *op)
return err
})
if err != nil {
return nil, err
}
@ -328,7 +343,12 @@ func (w *WalletKit) ReleaseOutput(ctx context.Context,
return nil, err
}
if err := w.cfg.Wallet.ReleaseOutput(lockID, *op); err != nil {
// Acquire the global coin selection lock to maintain consistency as
// it's acquired when we initially leased the output.
err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
return w.cfg.Wallet.ReleaseOutput(lockID, *op)
})
if err != nil {
return nil, err
}

View File

@ -950,7 +950,17 @@ func (r *rpcServer) ListUnspent(ctx context.Context,
// With our arguments validated, we'll query the internal wallet for
// the set of UTXOs that match our query.
utxos, err := r.server.cc.wallet.ListUnspentWitness(minConfs, maxConfs)
//
// We'll acquire the global coin selection lock to ensure there aren't
// any other concurrent processes attempting to lock any UTXOs which may
// be shown available to us.
var utxos []*lnwallet.Utxo
err = r.server.cc.wallet.WithCoinSelectLock(func() error {
utxos, err = r.server.cc.wallet.ListUnspentWitness(
minConfs, maxConfs,
)
return err
})
if err != nil {
return nil, err
}

View File

@ -153,6 +153,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, cc *chainControl
subCfgValue.FieldByName("Wallet").Set(
reflect.ValueOf(cc.wallet),
)
subCfgValue.FieldByName("CoinSelectionLocker").Set(
reflect.ValueOf(cc.wallet),
)
subCfgValue.FieldByName("KeyRing").Set(
reflect.ValueOf(cc.keyRing),
)