lnwallet: add new WithCoinSelectLock method to fix coin select race conditions

In this commit, we add a new method WithCoinSelectLock. This method will
allow us to fix bugs in the project atm that can arise if a channel
funding is attempted (either manually or by autopilot) while a users is
attempting to send an on-chain transaction. If this happens
concurrently, then both contexts will grab the set of UTXOs and attempt
to lock them one by one. However, since they didn't obtain an exclusive
snapshot of the UTXO set of the wallet, they may both attempt to lock
the same input.

We also ensure that calls to SendMany cannot run into this issue by
using the WithCoinSelectLock synchronization when attempting to instruct
the internal wallet to send payments.
This commit is contained in:
Olaoluwa Osuntokun 2018-11-17 21:06:59 -08:00
parent 9c29e61826
commit 4b316d97c8
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
2 changed files with 29 additions and 1 deletions

View File

@ -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

View File

@ -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
}