Skip to content

Commit 286ee95

Browse files
authored
Merge pull request #8800 from ProofOfKeags/bugfix/8535
contractcourt: consider delivery addresses when evaluating toSelfAmount
2 parents 931b3dc + 1fea14f commit 286ee95

File tree

7 files changed

+161
-14
lines changed

7 files changed

+161
-14
lines changed

contractcourt/chain_watcher.go

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package contractcourt
33
import (
44
"bytes"
55
"fmt"
6+
"slices"
67
"sync"
78
"sync/atomic"
89
"time"
@@ -16,8 +17,10 @@ import (
1617
"github.com/davecgh/go-spew/spew"
1718
"github.com/lightningnetwork/lnd/chainntnfs"
1819
"github.com/lightningnetwork/lnd/channeldb"
20+
"github.com/lightningnetwork/lnd/fn"
1921
"github.com/lightningnetwork/lnd/input"
2022
"github.com/lightningnetwork/lnd/lnwallet"
23+
"github.com/lightningnetwork/lnd/lnwire"
2124
)
2225

2326
const (
@@ -970,28 +973,67 @@ func (c *chainWatcher) handleUnknownRemoteState(
970973
}
971974

972975
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
973-
// to a script that the wallet controls. If no outputs pay to us, then we
976+
// to a script that the wallet controls or the channel defines as its delivery
977+
// script . If no outputs pay to us (determined by these criteria), then we
974978
// return zero. This is possible as our output may have been trimmed due to
975979
// being dust.
976980
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
977-
var selfAmt btcutil.Amount
978-
for _, txOut := range tx.TxOut {
981+
// There are two main cases we have to handle here. First, in the coop
982+
// close case we will always have saved the delivery address we used
983+
// whether it was from the upfront shutdown, from the delivery address
984+
// requested at close time, or even an automatically generated one. All
985+
// coop-close cases can be identified in the following manner:
986+
shutdown, _ := c.cfg.chanState.ShutdownInfo()
987+
oDeliveryAddr := fn.MapOption(
988+
func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
989+
return i.DeliveryScript.Val
990+
})(shutdown)
991+
992+
// Here we define a function capable of identifying whether an output
993+
// corresponds with our local delivery script from a ShutdownInfo if we
994+
// have a ShutdownInfo for this chainWatcher's underlying channel.
995+
//
996+
// isDeliveryOutput :: *TxOut -> bool
997+
isDeliveryOutput := func(o *wire.TxOut) bool {
998+
return fn.ElimOption(
999+
oDeliveryAddr,
1000+
// If we don't have a delivery addr, then the output
1001+
// can't match it.
1002+
func() bool { return false },
1003+
// Otherwise if the PkScript of the TxOut matches our
1004+
// delivery script then this is a delivery output.
1005+
func(a lnwire.DeliveryAddress) bool {
1006+
return slices.Equal(a, o.PkScript)
1007+
},
1008+
)
1009+
}
1010+
1011+
// Here we define a function capable of identifying whether an output
1012+
// belongs to the LND wallet. We use this as a heuristic in the case
1013+
// where we might be looking for spendable force closure outputs.
1014+
//
1015+
// isWalletOutput :: *TxOut -> bool
1016+
isWalletOutput := func(out *wire.TxOut) bool {
9791017
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
9801018
// Doesn't matter what net we actually pass in.
981-
txOut.PkScript, &chaincfg.TestNet3Params,
1019+
out.PkScript, &chaincfg.TestNet3Params,
9821020
)
9831021
if err != nil {
984-
continue
1022+
return false
9851023
}
9861024

987-
for _, addr := range addrs {
988-
if c.cfg.isOurAddr(addr) {
989-
selfAmt += btcutil.Amount(txOut.Value)
990-
}
991-
}
1025+
return fn.Any(c.cfg.isOurAddr, addrs)
9921026
}
9931027

994-
return selfAmt
1028+
// Grab all of the outputs that correspond with our delivery address
1029+
// or our wallet is aware of.
1030+
outs := fn.Filter(fn.PredOr(isDeliveryOutput, isWalletOutput), tx.TxOut)
1031+
1032+
// Grab the values for those outputs.
1033+
vals := fn.Map(func(o *wire.TxOut) int64 { return o.Value }, outs)
1034+
1035+
// Return the sum.
1036+
return btcutil.Amount(fn.Sum(vals))
9951037
}
9961038

9971039
// dispatchCooperativeClose processed a detect cooperative channel closure.

docs/release-notes/release-notes-0.18.1.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020
# Bug Fixes
2121

22+
* `closedchannels` now [successfully reports](https://github.com/lightningnetwork/lnd/pull/8800)
23+
settled balances even if the delivery address is set to an address that
24+
LND does not control.
25+
2226
# New Features
2327
## Functional Enhancements
2428
## RPC Additions

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ require (
3535
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f
3636
github.com/lightningnetwork/lnd/cert v1.2.2
3737
github.com/lightningnetwork/lnd/clock v1.1.1
38-
github.com/lightningnetwork/lnd/fn v1.0.5
38+
github.com/lightningnetwork/lnd/fn v1.0.9
3939
github.com/lightningnetwork/lnd/healthcheck v1.2.4
4040
github.com/lightningnetwork/lnd/kvdb v1.4.8
4141
github.com/lightningnetwork/lnd/queue v1.1.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,8 @@ github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf
448448
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
449449
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
450450
github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ=
451-
github.com/lightningnetwork/lnd/fn v1.0.5 h1:ffDgMSn83avw6rNzxhbt6w5/2oIrwQKTPGfyaLupZtE=
452-
github.com/lightningnetwork/lnd/fn v1.0.5/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U=
451+
github.com/lightningnetwork/lnd/fn v1.0.9 h1:VPljrzHGh0Wfs2NZe/ugUfH0hl6/L2eXW0LLXMUEy3s=
452+
github.com/lightningnetwork/lnd/fn v1.0.9/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U=
453453
github.com/lightningnetwork/lnd/healthcheck v1.2.4 h1:lLPLac+p/TllByxGSlkCwkJlkddqMP5UCoawCj3mgFQ=
454454
github.com/lightningnetwork/lnd/healthcheck v1.2.4/go.mod h1:G7Tst2tVvWo7cx6mSBEToQC5L1XOGxzZTPB29g9Rv2I=
455455
github.com/lightningnetwork/lnd/kvdb v1.4.8 h1:xH0a5Vi1yrcZ5BEeF2ba3vlKBRxrL9uYXlWTjOjbNTY=

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,4 +626,8 @@ var allTestCases = []*lntest.TestCase{
626626
Name: "sweep commit output and anchor",
627627
TestFunc: testSweepCommitOutputAndAnchor,
628628
},
629+
{
630+
Name: "coop close with external delivery",
631+
TestFunc: testCoopCloseWithExternalDelivery,
632+
},
629633
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package itest
2+
3+
import (
4+
"testing"
5+
6+
"github.com/btcsuite/btcd/btcutil"
7+
"github.com/lightningnetwork/lnd/lnrpc"
8+
"github.com/lightningnetwork/lnd/lntest"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest) {
13+
ht.Run("set delivery address at open", func(t *testing.T) {
14+
tt := ht.Subtest(t)
15+
testCoopCloseWithExternalDeliveryImpl(tt, true)
16+
})
17+
ht.Run("set delivery address at close", func(t *testing.T) {
18+
tt := ht.Subtest(t)
19+
testCoopCloseWithExternalDeliveryImpl(tt, false)
20+
})
21+
}
22+
23+
// testCoopCloseWithExternalDeliveryImpl ensures that we have a valid settled
24+
// balance irrespective of whether the delivery address is in LND's wallet or
25+
// not. Some users set this value to be an address in a different wallet and
26+
// this should not affect our ability to accurately report the settled balance.
27+
func testCoopCloseWithExternalDeliveryImpl(ht *lntest.HarnessTest,
28+
upfrontShutdown bool) {
29+
30+
alice, bob := ht.Alice, ht.Bob
31+
ht.ConnectNodes(alice, bob)
32+
33+
// Here we generate a final delivery address in bob's wallet but set by
34+
// alice. We do this to ensure that the address is not in alice's LND
35+
// wallet. We already correctly track settled balances when the address
36+
// is in the LND wallet.
37+
addr := bob.RPC.NewAddress(&lnrpc.NewAddressRequest{
38+
Type: lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH,
39+
})
40+
41+
// Prepare for channel open.
42+
openParams := lntest.OpenChannelParams{
43+
Amt: btcutil.Amount(1000000),
44+
}
45+
46+
// If we are testing the case where we set it on open then we'll set the
47+
// upfront shutdown script in the channel open parameters.
48+
if upfrontShutdown {
49+
openParams.CloseAddress = addr.Address
50+
}
51+
52+
// Open the channel!
53+
chanPoint := ht.OpenChannel(alice, bob, openParams)
54+
55+
// Prepare for channel close.
56+
closeParams := lnrpc.CloseChannelRequest{
57+
ChannelPoint: chanPoint,
58+
TargetConf: 6,
59+
}
60+
61+
// If we are testing the case where we set the delivery address on
62+
// channel close then we will set it in the channel close parameters.
63+
if !upfrontShutdown {
64+
closeParams.DeliveryAddress = addr.Address
65+
}
66+
67+
// Close the channel!
68+
closeClient := alice.RPC.CloseChannel(&closeParams)
69+
70+
// Assert that we got a channel update when we get a closing txid.
71+
_, err := closeClient.Recv()
72+
require.NoError(ht, err)
73+
74+
// Mine the closing transaction.
75+
ht.MineClosingTx(chanPoint)
76+
77+
// Assert that we got a channel update when the closing tx was mined.
78+
_, err = closeClient.Recv()
79+
require.NoError(ht, err)
80+
81+
// Here we query our closed channels to conduct the final test
82+
// assertion. We want to ensure that even though alice's delivery
83+
// address is set to an address in bob's wallet, we should still show
84+
// the balance as settled.
85+
closed := alice.RPC.ClosedChannels(&lnrpc.ClosedChannelsRequest{
86+
Cooperative: true,
87+
})
88+
89+
// The settled balance should never be zero at this point.
90+
require.NotZero(ht, len(closed.Channels))
91+
require.NotZero(ht, closed.Channels[0].SettledBalance)
92+
}

lntest/harness.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,10 @@ type OpenChannelParams struct {
10071007
// FundMax flag is specified the entirety of selected funds is
10081008
// allocated towards channel funding.
10091009
Outpoints []*lnrpc.OutPoint
1010+
1011+
// CloseAddress sets the upfront_shutdown_script parameter during
1012+
// channel open. It is expected to be encoded as a bitcoin address.
1013+
CloseAddress string
10101014
}
10111015

10121016
// prepareOpenChannel waits for both nodes to be synced to chain and returns an
@@ -1059,6 +1063,7 @@ func (h *HarnessTest) prepareOpenChannel(srcNode, destNode *node.HarnessNode,
10591063
FundMax: p.FundMax,
10601064
Memo: p.Memo,
10611065
Outpoints: p.Outpoints,
1066+
CloseAddress: p.CloseAddress,
10621067
}
10631068
}
10641069

0 commit comments

Comments
 (0)