Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,15 @@ type Config struct {
// NoDisconnectOnPongFailure controls if we'll disconnect if a peer
// doesn't respond to a pong in time.
NoDisconnectOnPongFailure bool `long:"no-disconnect-on-pong-failure" description:"If true, a peer will *not* be disconnected if a pong is not received in time or is mismatched. Defaults to false, meaning peers *will* be disconnected on pong failure."`

// UpfrontShutdownAddr specifies an address that our funds will be paid
// out to on cooperative channel close. This applies to all new channel
// opens unless overridden by an option in openchannel or by a channel
// acceptor.
// Note: If this field is set when opening a channel with a peer that
// does not advertise support for the upfront shutdown feature, the
// channel open will fail.
UpfrontShutdownAddr string `long:"upfront-shutdown-address" description:"The address to which funds will be paid out during a cooperative channel close. This applies to all channels opened after this option is set, unless overridden for a specific channel opening. Note: If this option is set, any channel opening will fail if the peer does not explicitly advertise support for the upfront-shutdown feature bit."`
}

// GRPCConfig holds the configuration options for the gRPC server.
Expand Down
7 changes: 7 additions & 0 deletions docs/release-notes/release-notes-0.21.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
# Improvements
## Functional Updates

* [Added support](https://github.com/lightningnetwork/lnd/pull/9432) for the
`upfront-shutdown-address` configuration in `lnd.conf`, allowing users to
specify an address for cooperative channel closures where funds will be sent.
This applies to both funders and fundees, with the ability to override the
value during channel opening or acceptance.

## RPC Updates

## lncli Updates
Expand Down Expand Up @@ -65,3 +71,4 @@
# Contributors (Alphabetical Order)

* Elle Mouton
* Nishant Bansal
31 changes: 29 additions & 2 deletions funding/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,10 @@ type Config struct {
// implementations to inject and process custom records over channel
// related wire messages.
AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator]

// ShutdownScript is an optional upfront-shutdown script to which our
// funds should be paid on a cooperative close.
ShutdownScript fn.Option[lnwire.DeliveryAddress]
}

// Manager acts as an orchestrator/bridge between the wallet's
Expand Down Expand Up @@ -1760,12 +1764,24 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
return
}

// If the fundee didn't provide an upfront-shutdown address via
// the channel acceptor, fall back to the configured shutdown
// script (if any).
shutdownScript := acceptorResp.UpfrontShutdown
if len(shutdownScript) == 0 {
f.cfg.ShutdownScript.WhenSome(
func(script lnwire.DeliveryAddress) {
shutdownScript = script
},
)
}

// Check whether the peer supports upfront shutdown, and get a new
// wallet address if our node is configured to set shutdown addresses by
// default. We use the upfront shutdown script provided by our channel
// acceptor (if any) in lieu of user input.
shutdown, err := getUpfrontShutdownScript(
f.cfg.EnableUpfrontShutdown, peer, acceptorResp.UpfrontShutdown,
f.cfg.EnableUpfrontShutdown, peer, shutdownScript,
f.selectShutdownScript,
)
if err != nil {
Expand Down Expand Up @@ -4849,12 +4865,23 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
}
}

// If the funder did not provide an upfront-shutdown address, fall back
// to the configured shutdown script (if any).
shutdownScript := msg.ShutdownScript
if len(shutdownScript) == 0 {
f.cfg.ShutdownScript.WhenSome(
func(script lnwire.DeliveryAddress) {
shutdownScript = script
},
)
}

// Check whether the peer supports upfront shutdown, and get an address
// which should be used (either a user specified address or a new
// address from the wallet if our node is configured to set shutdown
// address by default).
shutdown, err := getUpfrontShutdownScript(
f.cfg.EnableUpfrontShutdown, msg.Peer, msg.ShutdownScript,
f.cfg.EnableUpfrontShutdown, msg.Peer, shutdownScript,
f.selectShutdownScript,
)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions itest/list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ var allTestCases = []*lntest.TestCase{
Name: "open channel reorg test",
TestFunc: testOpenChannelAfterReorg,
},
{
Name: "open channel with shutdown address",
TestFunc: testOpenChannelWithShutdownAddr,
},
{
Name: "sign psbt",
TestFunc: testSignPsbt,
Expand Down
73 changes: 73 additions & 0 deletions itest/lnd_open_channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1269,3 +1269,76 @@ func testFundingManagerFundingTimeout(ht *lntest.HarnessTest) {
// Cleanup the mempool by mining blocks.
ht.MineBlocksAndAssertNumTxes(6, 1)
}

// testOpenChannelWithShutdownAddr verifies that if the funder or fundee
// specifies an upfront shutdown address in the config, the funds are correctly
// transferred to the specified address during channel closure.
func testOpenChannelWithShutdownAddr(ht *lntest.HarnessTest) {
const (
// Channel funding amount in sat.
channelAmount int64 = 100000

// Payment amount in sat.
paymentAmount int64 = 50000
)

// Create nodes for testing, ensuring Alice has sufficient initial
// funds.
alice := ht.NewNodeWithCoins("Alice", nil)
bob := ht.NewNode("Bob", nil)

// Generate upfront shutdown addresses for both nodes.
aliceShutdownAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH,
})
bobShutdownAddr := bob.RPC.NewAddress(&lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH,
})

// Update nodes with upfront shutdown addresses and restart them.
aliceNodeArgs := []string{
fmt.Sprintf(
"--upfront-shutdown-address=%s",
aliceShutdownAddr.Address,
),
}
ht.RestartNodeWithExtraArgs(alice, aliceNodeArgs)

bobNodeArgs := []string{
fmt.Sprintf(
"--upfront-shutdown-address=%s",
bobShutdownAddr.Address,
),
}
ht.RestartNodeWithExtraArgs(bob, bobNodeArgs)

// Connect Alice and Bob.
ht.ConnectNodes(alice, bob)

// Open a channel between Alice and Bob.
openChannelParams := lntest.OpenChannelParams{
Amt: btcutil.Amount(channelAmount),
PushAmt: btcutil.Amount(paymentAmount),
}
channelPoint := ht.OpenChannel(alice, bob, openChannelParams)

// Now close out the channel and obtain the raw closing TX.
closingTxid := ht.CloseChannel(alice, channelPoint)
closingTx := ht.GetRawTransaction(closingTxid).MsgTx()

// Calculate Alice's updated balance.
aliceFee := ht.CalculateTxFee(closingTx)
aliceExpectedBalance := channelAmount - paymentAmount - int64(aliceFee)

// Ensure Alice sees the change output in the list of unspent outputs.
// We expect 6 confirmed UTXOs, as 5 UTXOs of 1 BTC each were sent to
// the node during NewNodeWithCoins.
aliceUTXOConfirmed := ht.AssertNumUTXOsConfirmed(alice, 6)[0]
require.Equal(ht, aliceShutdownAddr.Address, aliceUTXOConfirmed.Address)
require.Equal(ht, aliceExpectedBalance, aliceUTXOConfirmed.AmountSat)

// Ensure Bob see the change output in the list of unspent outputs.
bobUTXOConfirmed := ht.AssertNumUTXOsConfirmed(bob, 1)[0]
require.Equal(ht, bobShutdownAddr.Address, bobUTXOConfirmed.Address)
require.Equal(ht, paymentAmount, bobUTXOConfirmed.AmountSat)
}
8 changes: 4 additions & 4 deletions peer/brontide.go
Original file line number Diff line number Diff line change
Expand Up @@ -3589,9 +3589,9 @@ func (p *Brontide) initNegotiateChanCloser(req *htlcswitch.ChanClose,
return nil
}

// chooseAddr returns the provided address if it is non-zero length, otherwise
// ChooseAddr returns the provided address if it is non-zero length, otherwise
// None.
func chooseAddr(addr lnwire.DeliveryAddress) fn.Option[lnwire.DeliveryAddress] {
func ChooseAddr(addr lnwire.DeliveryAddress) fn.Option[lnwire.DeliveryAddress] {
if len(addr) == 0 {
return fn.None[lnwire.DeliveryAddress]()
}
Expand Down Expand Up @@ -3930,10 +3930,10 @@ func (p *Brontide) initRbfChanCloser(
ChanType: channel.ChanType(),
DefaultFeeRate: defaultFeePerKw.FeePerVByte(),
ThawHeight: fn.Some(thawHeight),
RemoteUpfrontShutdown: chooseAddr(
RemoteUpfrontShutdown: ChooseAddr(
channel.RemoteUpfrontShutdownScript(),
),
LocalUpfrontShutdown: chooseAddr(
LocalUpfrontShutdown: ChooseAddr(
channel.LocalUpfrontShutdownScript(),
),
NewDeliveryScript: func() (lnwire.DeliveryAddress, error) {
Expand Down
9 changes: 9 additions & 0 deletions sample-lnd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,15 @@
; pong failure.
; no-disconnect-on-pong-failure=false

; The address to which funds will be paid out during a cooperative channel
; close. This applies to all channels opened after this option is set, unless
; overridden for a specific channel opening.
;
; Note: If this option is set, any channel opening will fail if the peer does
; not explicitly advertise support for the upfront-shutdown feature bit.
; upfront-shutdown-address=


[fee]

; Optional URL for external fee estimation. If no URL is specified, the method
Expand Down
11 changes: 11 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import (
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/lnwire"
Expand Down Expand Up @@ -1445,6 +1446,15 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
devCfg, reservationTimeout, zombieSweeperInterval)
}

// Attempt to parse the provided upfront-shutdown address (if any).
script, err := chancloser.ParseUpfrontShutdownAddress(
cfg.UpfrontShutdownAddr, cfg.ActiveNetParams.Params,
)
if err != nil {
return nil, fmt.Errorf("error parsing upfront shutdown: %w",
err)
}

//nolint:ll
s.fundingMgr, err = funding.NewFundingManager(funding.Config{
Dev: devCfg,
Expand Down Expand Up @@ -1623,6 +1633,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
AuxSigner: implCfg.AuxSigner,
AuxResolver: implCfg.AuxContractResolver,
AuxChannelNegotiator: implCfg.AuxChannelNegotiator,
ShutdownScript: peer.ChooseAddr(script),
})
if err != nil {
return nil, err
Expand Down
Loading