lnwallet+funding+rpcserver: check reserved value before

PublishTransaction

For a few manual send cases that can be initiated by the user, we check
the reserved value.
This commit is contained in:
Johan T. Halseth 2021-01-10 19:54:49 +01:00
parent 185ba77f8e
commit 422008e3c0
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
2 changed files with 112 additions and 2 deletions

@ -986,6 +986,27 @@ func (l *LightningWallet) CheckReservedValue(in []wire.OutPoint,
return reserved, nil
}
// CheckReservedValueTx calls CheckReservedValue with the inputs and outputs
// from the given tx, with the number of anchor channels currently open in the
// database.
//
// NOTE: This method should only be run with the CoinSelectLock held.
func (l *LightningWallet) CheckReservedValueTx(tx *wire.MsgTx) (btcutil.Amount,
error) {
numAnchors, err := l.currentNumAnchorChans()
if err != nil {
return 0, err
}
var inputs []wire.OutPoint
for _, txIn := range tx.TxIn {
inputs = append(inputs, txIn.PreviousOutPoint)
}
return l.CheckReservedValue(inputs, tx.TxOut, numAnchors)
}
// initOurContribution initializes the given ChannelReservation with our coins
// and change reserved for the channel, and derives the keys to use for this
// channel.

@ -1056,7 +1056,25 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
return nil, err
}
tx, err := r.server.cc.Wallet.SendOutputs(outputs, feeRate, minconf, label)
// We first do a dry run, to sanity check we won't spend our wallet
// balance below the reserved amount.
authoredTx, err := r.server.cc.Wallet.CreateSimpleTx(
outputs, feeRate, true,
)
if err != nil {
return nil, err
}
_, err = r.server.cc.Wallet.CheckReservedValueTx(authoredTx.Tx)
if err != nil {
return nil, err
}
// If that checks out, we're failry confident that creating sending to
// these outputs will keep the wallet balance above the reserve.
tx, err := r.server.cc.Wallet.SendOutputs(
outputs, feeRate, minconf, label,
)
if err != nil {
return nil, err
}
@ -1253,7 +1271,9 @@ func (r *rpcServer) SendCoins(ctx context.Context,
// With the sweeper instance created, we can now generate a
// transaction that will sweep ALL outputs from the wallet in a
// single transaction. This will be generated in a concurrent
// safe manner, so no need to worry about locking.
// safe manner, so no need to worry about locking. The tx will
// pay to the change address created above if we needed to
// reserve any value, the rest will go to targetAddr.
sweepTxPkg, err := sweep.CraftSweepAllTx(
feePerKw, lnwallet.DefaultDustLimit(),
uint32(bestHeight), nil, targetAddr, wallet,
@ -1264,6 +1284,75 @@ func (r *rpcServer) SendCoins(ctx context.Context,
return nil, err
}
// Before we publish the transaction we make sure it won't
// violate our reserved wallet value.
var reservedVal btcutil.Amount
err = wallet.WithCoinSelectLock(func() error {
var err error
reservedVal, err = wallet.CheckReservedValueTx(
sweepTxPkg.SweepTx,
)
return err
})
// If sending everything to this address would invalidate our
// reserved wallet balance, we create a new sweep tx, where
// we'll send the reserved value back to our wallet.
if err == lnwallet.ErrReservedValueInvalidated {
sweepTxPkg.CancelSweepAttempt()
rpcsLog.Debugf("Reserved value %v not satisfied after "+
"send_all, trying with change output",
reservedVal)
// We'll request a change address from the wallet,
// where we'll send this reserved value back to. This
// ensures this is an address the wallet knows about,
// allowing us to pass the reserved value check.
changeAddr, err := r.server.cc.Wallet.NewAddress(
lnwallet.WitnessPubKey, true,
)
if err != nil {
return nil, err
}
// Send the reserved value to this change address, the
// remaining funds will go to the targetAddr.
outputs := []sweep.DeliveryAddr{
{
Addr: changeAddr,
Amt: reservedVal,
},
}
sweepTxPkg, err = sweep.CraftSweepAllTx(
feePerKw, lnwallet.DefaultDustLimit(),
uint32(bestHeight), outputs, targetAddr, wallet,
wallet.WalletController, wallet.WalletController,
r.server.cc.FeeEstimator, r.server.cc.Signer,
)
if err != nil {
return nil, err
}
// Sanity check the new tx by re-doing the check.
err = wallet.WithCoinSelectLock(func() error {
_, err := wallet.CheckReservedValueTx(
sweepTxPkg.SweepTx,
)
return err
})
if err != nil {
sweepTxPkg.CancelSweepAttempt()
return nil, err
}
} else if err != nil {
sweepTxPkg.CancelSweepAttempt()
return nil, err
}
rpcsLog.Debugf("Sweeping all coins from wallet to addr=%v, "+
"with tx=%v", in.Addr, spew.Sdump(sweepTxPkg.SweepTx))