diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 6f7863dc5..2dabf056d 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -1252,6 +1252,17 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { l.limboMtx.Unlock() } +// WithCoinSelectLock will execute the passed function closure in a +// synchronized manner preventing any coin selection operations from proceeding +// while the closure if executing. This can be seen as the ability to execute a +// function closure under an exclusive coin selection lock. +func (l *LightningWallet) WithCoinSelectLock(f func() error) error { + l.coinSelectMtx.Lock() + defer l.coinSelectMtx.Unlock() + + return f() +} + // selectCoinsAndChange performs coin selection in order to obtain witness // outputs which sum to at least 'numCoins' amount of satoshis. If coin // selection is successful/possible, then the selected coins are available diff --git a/rpcserver.go b/rpcserver.go index dfd3e8905..a49fea0a4 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -804,7 +804,24 @@ func (r *rpcServer) SendMany(ctx context.Context, rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v", spew.Sdump(in.AddrToAmount), int64(feePerKw)) - txid, err := r.sendCoinsOnChain(in.AddrToAmount, feePerKw) + var txid *chainhash.Hash + + // We'll attempt to send to the target set of outputs, ensuring that we + // synchronize with any other ongoing coin selection attempts which + // happen to also be concurrently executing. + wallet := r.server.cc.wallet + err = wallet.WithCoinSelectLock(func() error { + sendManyTXID, err := r.sendCoinsOnChain( + in.AddrToAmount, feePerKw, + ) + if err != nil { + return err + } + + txid = sendManyTXID + + return nil + }) if err != nil { return nil, err }