From 0e5b3e77cb084f5fe704af9a47ef273cfaf63fb4 Mon Sep 17 00:00:00 2001 From: Bjarne Magnussen Date: Thu, 22 Apr 2021 20:14:44 +0200 Subject: [PATCH] lnd: handles the `FundMax` field inside `parseOpenChannelReq` Adds handling of the `FundMax` field when parsing an `OpenChannelRequest` with `rpcServer.parseOpenChannelReq`. --- funding/manager.go | 9 ++++ rpcserver.go | 103 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 83c9a1d21..5536353b9 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -266,6 +266,15 @@ type InitFundingMsg struct { // peer. MaxLocalCsv uint16 + // FundUpToMaxAmt is the maximum amount to try to commit to. If set, the + // MinFundAmt field denotes the acceptable minimum amount to commit to, + // while trying to commit as many coins as possible up to this value. + FundUpToMaxAmt btcutil.Amount + + // MinFundAmt must be set iff FundUpToMaxAmt is set. It denotes the + // minimum amount to commit to. + MinFundAmt btcutil.Amount + // ChanFunder is an optional channel funder that allows the caller to // control exactly how the channel funding is carried out. If not // specified, then the default chanfunding.WalletAssembler will be diff --git a/rpcserver.go b/rpcserver.go index c1d1b7b7c..528ca18d2 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1934,6 +1934,62 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, localFundingAmt := btcutil.Amount(in.LocalFundingAmount) remoteInitialBalance := btcutil.Amount(in.PushSat) + + // If we are not committing the maximum viable balance towards a channel + // then the local funding amount must be specified. In case FundMax is + // set the funding amount is specified as the interval between minimum + // funding amount and by the configured maximum channel size. + if !in.FundMax && localFundingAmt == 0 { + return nil, fmt.Errorf("local funding amount must be non-zero") + } + + // Ensure that the initial balance of the remote party (if pushing + // satoshis) does not exceed the amount the local party has requested + // for funding. This is only checked if we are not committing the + // maximum viable amount towards the channel balance. If we do commit + // the maximum then the remote balance is checked in a dedicated FundMax + // check. + if !in.FundMax && remoteInitialBalance >= localFundingAmt { + return nil, fmt.Errorf("amount pushed to remote peer for " + + "initial state must be below the local funding amount") + } + + // We either allow the fundmax or the psbt flow hence we return an error + // if both are set. + if in.FundingShim != nil && in.FundMax { + return nil, fmt.Errorf("cannot provide a psbt funding shim " + + "while committing the maxium wallet balance towards " + + "the channel opening") + } + + // If the FundMax flag is set, ensure that the acceptable minimum local + // amount adheres to the amount to be pushed to the remote, and to + // current rules, while also respecting the settings for the maximum + // channel size. + var minFundAmt, fundUpToMaxAmt btcutil.Amount + if in.FundMax { + // We assume the configured maximum channel size to be the upper + // bound of our "maxed" out funding attempt. + fundUpToMaxAmt = btcutil.Amount(r.cfg.MaxChanSize) + + // Since the standard non-fundmax flow requires the minimum + // funding amount to be at least in the amount of the initial + // remote balance(push amount) we need to adjust the minimum + // funding amount accordingly. We initially assume the minimum + // allowed channel size as minimum funding amount. + minFundAmt = funding.MinChanFundingSize + + // If minFundAmt is less than the initial remote balance we + // simply assign the initial remote balance to minFundAmt in + // order to fullfil the criterion. Whether or not this so + // determined minimum amount is actually available is + // ascertained downstream in the lnwallet's reservation + // workflow. + if remoteInitialBalance >= minFundAmt { + minFundAmt = remoteInitialBalance + } + } + minHtlcIn := lnwire.MilliSatoshi(in.MinHtlcMsat) remoteCsvDelay := uint16(in.RemoteCsvDelay) maxValue := lnwire.MilliSatoshi(in.RemoteMaxValueInFlightMsat) @@ -1942,20 +1998,6 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn) - // Ensure that a local funding amount has been specified. - if localFundingAmt == 0 { - return nil, fmt.Errorf("local funding amount must be non-zero") - } - - // Ensure that the initial balance of the remote party (if pushing - // satoshis) does not exceed the amount the local party has requested - // for funding. - // - if remoteInitialBalance >= localFundingAmt { - return nil, fmt.Errorf("amount pushed to remote peer for " + - "initial state must be below the local funding amount") - } - // Determine if the user provided channel fees // and if so pass them on to the funding workflow. var channelBaseFee, channelFeeRate *uint64 @@ -1968,7 +2010,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // Ensure that the remote channel reserve does not exceed 20% of the // channel capacity. - if remoteChanReserve >= localFundingAmt/5 { + if !in.FundMax && remoteChanReserve >= localFundingAmt/5 { return nil, fmt.Errorf("remote channel reserve must be less " + "than the %%20 of the channel capacity") } @@ -1976,18 +2018,25 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // Ensure that the user doesn't exceed the current soft-limit for // channel size. If the funding amount is above the soft-limit, then // we'll reject the request. + // If the FundMax flag is set the local amount is determined downstream + // in the wallet hence we do not check it here against the maximum + // funding amount. Only if the localFundingAmt is specified we can check + // if it exceeds the maximum funding amount. wumboEnabled := globalFeatureSet.HasFeature( lnwire.WumboChannelsOptional, ) - if !wumboEnabled && localFundingAmt > MaxFundingAmount { + if !in.FundMax && !wumboEnabled && localFundingAmt > MaxFundingAmount { return nil, fmt.Errorf("funding amount is too large, the max "+ "channel size is: %v", MaxFundingAmount) } // Restrict the size of the channel we'll actually open. At a later - // level, we'll ensure that the output we create after accounting for - // fees that a dust output isn't created. - if localFundingAmt < funding.MinChanFundingSize { + // level, we'll ensure that the output we create, after accounting for + // fees, does not leave a dust output. In case of the FundMax flow + // dedicated checks ensure that the lower boundary of the channel size + // is at least in the amount of MinChanFundingSize or potentially higher + // if a remote balance is specified. + if !in.FundMax && localFundingAmt < funding.MinChanFundingSize { return nil, fmt.Errorf("channel is too small, the minimum "+ "channel size is: %v SAT", int64(funding.MinChanFundingSize)) } @@ -2128,12 +2177,14 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // open a new channel. A stream is returned in place, this stream will // be used to consume updates of the state of the pending channel. return &funding.InitFundingMsg{ - TargetPubkey: nodePubKey, - ChainHash: *r.cfg.ActiveNetParams.GenesisHash, - LocalFundingAmt: localFundingAmt, - BaseFee: channelBaseFee, - FeeRate: channelFeeRate, - PushAmt: lnwire.NewMSatFromSatoshis(remoteInitialBalance), + TargetPubkey: nodePubKey, + ChainHash: *r.cfg.ActiveNetParams.GenesisHash, + LocalFundingAmt: localFundingAmt, + BaseFee: channelBaseFee, + FeeRate: channelFeeRate, + PushAmt: lnwire.NewMSatFromSatoshis( + remoteInitialBalance, + ), MinHtlcIn: minHtlcIn, FundingFeePerKw: feeRate, Private: in.Private, @@ -2145,6 +2196,8 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, MaxHtlcs: maxHtlcs, MaxLocalCsv: uint16(in.MaxLocalCsv), ChannelType: channelType, + FundUpToMaxAmt: fundUpToMaxAmt, + MinFundAmt: minFundAmt, }, nil }