From c2f1fe26c1a6af59fbb51d8a6160e45bbe1765c3 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 9 Jun 2020 19:37:17 -0700 Subject: [PATCH] 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. --- lnrpc/walletrpc/config_active.go | 7 +++++++ lnrpc/walletrpc/walletkit_server.go | 26 +++++++++++++++++++++++--- rpcserver.go | 12 +++++++++++- subrpcserver_config.go | 3 +++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lnrpc/walletrpc/config_active.go b/lnrpc/walletrpc/config_active.go index 103e533b..f285f75d 100644 --- a/lnrpc/walletrpc/config_active.go +++ b/lnrpc/walletrpc/config_active.go @@ -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 diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 71a144f3..a52bb920 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -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 } diff --git a/rpcserver.go b/rpcserver.go index f8e713ab..6e5e5fa1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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 } diff --git a/subrpcserver_config.go b/subrpcserver_config.go index dcbbd2e8..26ecc3fe 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -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), )