From 30e10322b289e54bc92590691962ee486f0d126a Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Fri, 31 May 2024 15:06:01 -0700 Subject: [PATCH] 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. --- contractcourt/chain_watcher.go | 74 ++++++++++++++++++++++++++-------- go.mod | 2 +- go.sum | 4 +- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 55ac0979c..b17b40aca 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -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. diff --git a/go.mod b/go.mod index 55898d79a..8d0683932 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 5a21d5439..2f83baf01 100644 --- a/go.sum +++ b/go.sum @@ -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=