From e4c4d946fd51080bba2d99d8bf5c02026ab60313 Mon Sep 17 00:00:00 2001 From: Nishant Bansal Date: Mon, 20 Oct 2025 00:27:38 +0530 Subject: [PATCH] multi: add new config option `upfront-shutdown-address` Introduced a new config value `upfront-shutdown-address` in the `lnd.conf` file. This ensures that channel close funds are transferred to the specified shutdown address. The value applies to both the funder and the fundee but can be overridden by the value specified during `openchannel` or by the `channel acceptor`. NOTE: If this field is set when opening a channel with a peer that does not advertise support for upfront shutdown feature, the channel open will fail. Signed-off-by: Nishant Bansal --- config.go | 9 +++++++++ funding/manager.go | 31 +++++++++++++++++++++++++++++-- peer/brontide.go | 8 ++++---- sample-lnd.conf | 9 +++++++++ server.go | 11 +++++++++++ 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index 6d5bc546a..e9ce41035 100644 --- a/config.go +++ b/config.go @@ -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. diff --git a/funding/manager.go b/funding/manager.go index 8176e6aa2..5711ed8e2 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -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 @@ -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 { @@ -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 { diff --git a/peer/brontide.go b/peer/brontide.go index 4a196fb95..8d02ca6e5 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -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]() } @@ -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) { diff --git a/sample-lnd.conf b/sample-lnd.conf index c3b3a96b1..ed5dabd43 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -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 diff --git a/server.go b/server.go index d0289de2a..3c011f8b0 100644 --- a/server.go +++ b/server.go @@ -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" @@ -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, @@ -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