contractcourt: consider delivery addresses when evaluating toSelfAmount

This commit fixes #8535 by changing how we assess toSelfAmount inside
the chainWatcher.

In certain cases users may wish to close out channel funds to external
delivery addresses set either during open or close.

Prior to this change we only consider addresses that our wallet is
aware of.

This change now identifies outputs as to_self outputs if the delivery
script matches OR if our wallet is aware of the address. In certain
edge cases it can be possible for there to be more than one output
that matches these criteria and in that case we will return the sum
of those values.
This commit is contained in:
Keagan McClelland 2024-05-31 15:06:01 -07:00
parent 56048133f2
commit 30e10322b2
3 changed files with 61 additions and 19 deletions

View File

@ -3,6 +3,7 @@ package contractcourt
import (
"bytes"
"fmt"
"slices"
"sync"
"sync/atomic"
"time"
@ -16,8 +17,10 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
@ -970,28 +973,67 @@ func (c *chainWatcher) handleUnknownRemoteState(
}
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
// to a script that the wallet controls. If no outputs pay to us, then we
// to a script that the wallet controls or the channel defines as its delivery
// script . If no outputs pay to us (determined by these criteria), then we
// return zero. This is possible as our output may have been trimmed due to
// being dust.
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
var selfAmt btcutil.Amount
for _, txOut := range tx.TxOut {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
// Doesn't matter what net we actually pass in.
txOut.PkScript, &chaincfg.TestNet3Params,
)
if err != nil {
continue
}
// There are two main cases we have to handle here. First, in the coop
// close case we will always have saved the delivery address we used
// whether it was from the upfront shutdown, from the delivery address
// requested at close time, or even an automatically generated one. All
// coop-close cases can be identified in the following manner:
shutdown, _ := c.cfg.chanState.ShutdownInfo()
oDeliveryAddr := fn.MapOption(
func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
return i.DeliveryScript.Val
})(shutdown)
for _, addr := range addrs {
if c.cfg.isOurAddr(addr) {
selfAmt += btcutil.Amount(txOut.Value)
}
}
// Here we define a function capable of identifying whether an output
// corresponds with our local delivery script from a ShutdownInfo if we
// have a ShutdownInfo for this chainWatcher's underlying channel.
//
// isDeliveryOutput :: *TxOut -> bool
isDeliveryOutput := func(o *wire.TxOut) bool {
return fn.ElimOption(
oDeliveryAddr,
// If we don't have a delivery addr, then the output
// can't match it.
func() bool { return false },
// Otherwise if the PkScript of the TxOut matches our
// delivery script then this is a delivery output.
func(a lnwire.DeliveryAddress) bool {
return slices.Equal(a, o.PkScript)
},
)
}
return selfAmt
// Here we define a function capable of identifying whether an output
// belongs to the LND wallet. We use this as a heuristic in the case
// where we might be looking for spendable force closure outputs.
//
// isWalletOutput :: *TxOut -> bool
isWalletOutput := func(out *wire.TxOut) bool {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
// Doesn't matter what net we actually pass in.
out.PkScript, &chaincfg.TestNet3Params,
)
if err != nil {
return false
}
return fn.Any(c.cfg.isOurAddr, addrs)
}
// Grab all of the outputs that correspond with our delivery address
// or our wallet is aware of.
outs := fn.Filter(fn.PredOr(isDeliveryOutput, isWalletOutput), tx.TxOut)
// Grab the values for those outputs.
vals := fn.Map(func(o *wire.TxOut) int64 { return o.Value }, outs)
// Return the sum.
return btcutil.Amount(fn.Sum(vals))
}
// dispatchCooperativeClose processed a detect cooperative channel closure.

2
go.mod
View File

@ -35,7 +35,7 @@ require (
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f
github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/clock v1.1.1
github.com/lightningnetwork/lnd/fn v1.0.5
github.com/lightningnetwork/lnd/fn v1.0.9
github.com/lightningnetwork/lnd/healthcheck v1.2.4
github.com/lightningnetwork/lnd/kvdb v1.4.8
github.com/lightningnetwork/lnd/queue v1.1.1

4
go.sum
View File

@ -448,8 +448,8 @@ github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ=
github.com/lightningnetwork/lnd/fn v1.0.5 h1:ffDgMSn83avw6rNzxhbt6w5/2oIrwQKTPGfyaLupZtE=
github.com/lightningnetwork/lnd/fn v1.0.5/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U=
github.com/lightningnetwork/lnd/fn v1.0.9 h1:VPljrzHGh0Wfs2NZe/ugUfH0hl6/L2eXW0LLXMUEy3s=
github.com/lightningnetwork/lnd/fn v1.0.9/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U=
github.com/lightningnetwork/lnd/healthcheck v1.2.4 h1:lLPLac+p/TllByxGSlkCwkJlkddqMP5UCoawCj3mgFQ=
github.com/lightningnetwork/lnd/healthcheck v1.2.4/go.mod h1:G7Tst2tVvWo7cx6mSBEToQC5L1XOGxzZTPB29g9Rv2I=
github.com/lightningnetwork/lnd/kvdb v1.4.8 h1:xH0a5Vi1yrcZ5BEeF2ba3vlKBRxrL9uYXlWTjOjbNTY=