diff --git a/go.mod b/go.mod index e417a2c61..903e369a1 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 github.com/lightninglabs/lndclient v1.0.1-0.20240725080034-64a756aa4c36 github.com/lightninglabs/neutrino/cache v1.1.2 - github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4 + github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240919091721-70580403898e github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 github.com/lightningnetwork/lnd/fn v1.1.0 diff --git a/go.sum b/go.sum index 7fae92d46..b9943d903 100644 --- a/go.sum +++ b/go.sum @@ -490,8 +490,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wl github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4 h1:LPnz0JxnzXJvCro714eBanzO7FKx5HF0ldU++zIu9yY= -github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4/go.mod h1:0gen58n0DVnqJJqCMN3AXNtqWRT0KltQanlvehnhCq0= +github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240919091721-70580403898e h1:Weu9TWNEIpC4XLbcUoSFK3Pv2aUSwn7NlYZKdsm8wUU= +github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240919091721-70580403898e/go.mod h1:/Uh0qCiU/oQls68spxpmP0kRjX/uGkLzt7P/uPpDofE= github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= 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= diff --git a/server.go b/server.go index 616a4d7e5..4e10fbc13 100644 --- a/server.go +++ b/server.go @@ -990,7 +990,8 @@ func (s *Server) HandleTraffic(cid lnwire.ShortChannelID, // // NOTE: This method is part of the routing.TlvTrafficShaper interface. func (s *Server) PaymentBandwidth(htlcBlob, commitmentBlob lfn.Option[tlv.Blob], - linkBandwidth lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { + linkBandwidth, + htlcAmt lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { srvrLog.Debugf("PaymentBandwidth called, htlcBlob=%v, "+ "commitmentBlob=%v", spew.Sdump(htlcBlob), @@ -1001,7 +1002,7 @@ func (s *Server) PaymentBandwidth(htlcBlob, commitmentBlob lfn.Option[tlv.Blob], } return s.cfg.AuxTrafficShaper.PaymentBandwidth( - htlcBlob, commitmentBlob, linkBandwidth, + htlcBlob, commitmentBlob, linkBandwidth, htlcAmt, ) } diff --git a/tapchannel/aux_traffic_shaper.go b/tapchannel/aux_traffic_shaper.go index 08b7b82e0..47259004f 100644 --- a/tapchannel/aux_traffic_shaper.go +++ b/tapchannel/aux_traffic_shaper.go @@ -120,8 +120,8 @@ func (s *AuxTrafficShaper) HandleTraffic(_ lnwire.ShortChannelID, // should be handled by the traffic shaper, the HandleTraffic method should be // called first. func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, - commitmentBlob lfn.Option[tlv.Blob], - linkBandwidth lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { + commitmentBlob lfn.Option[tlv.Blob], linkBandwidth, + htlcAmt lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { // If the commitment or HTLC blob is not set, we don't have any // information about the channel and cannot determine the available @@ -140,6 +140,28 @@ func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, return linkBandwidth, nil } + // Get the minimum HTLC amount, which is just above dust. + minHtlcAmt := lnwire.NewMSatFromSatoshis(DefaultOnChainHtlcAmount) + + // LND calls this hook twice. Once to see if the overall budget of the + // node is enough, and then during pathfinding to actually see if + // there's enough balance in the channel to make the payment attempt. + // + // When doing the overall balance check, we don't know what the actual + // htlcAmt is in satoshis, so a value of 0 will be passed here. Let's at + // least check if we can afford the min amount above dust. If the actual + // htlc amount ends up being greater when calling this method during + // pathfinding, we will still check it below. + + // If the passed htlcAmt is below dust, then assume the dust amount. At + // this point we know we are sending assets, so we cannot anchor them to + // dust amounts. Dust HTLCs are added to the fees and aren't + // materialized in an on-chain output, so we wouldn't have anything + // to anchor the asset commitment to. + if htlcAmt < minHtlcAmt { + htlcAmt = minHtlcAmt + } + commitment, err := cmsg.DecodeCommitment(commitmentBytes) if err != nil { return 0, fmt.Errorf("error decoding commitment blob: %w", err) @@ -161,6 +183,14 @@ func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, // the amount. htlcAssetAmount := htlc.Amounts.Val.Sum() if htlcAssetAmount != 0 && htlcAssetAmount <= localBalance { + // Check if the current link bandwidth can afford sending out + // the htlc amount without dipping into the channel reserve. If + // it goes below the reserve, we report zero bandwdith as we + // cannot push the htlc amount. + if linkBandwidth < htlcAmt { + return 0, nil + } + // We signal "infinite" bandwidth by returning a very high // value (number of Satoshis ever in existence), since we might // not have a quote available to know what the asset amount @@ -190,6 +220,14 @@ func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, mSatPerAssetUnit := quote.BidPrice + // At this point we have acquired what we need to express the asset + // bandwidth expressed in satoshis. Before we return the result, we need + // to check if the link bandwidth can afford sending a non-dust htlc to + // the other side. + if linkBandwidth < minHtlcAmt { + return 0, nil + } + // The available balance is the local asset unit expressed in // milli-satoshis. return lnwire.MilliSatoshi(localBalance) * mSatPerAssetUnit, nil diff --git a/tapchannel/commitment.go b/tapchannel/commitment.go index 21035acea..a2ed43a60 100644 --- a/tapchannel/commitment.go +++ b/tapchannel/commitment.go @@ -630,6 +630,11 @@ func CreateAllocations(chanState *channeldb.OpenChannel, ourBalance, leaseExpiry = chanState.ThawHeight } + dustLimit := chanState.LocalChanCfg.DustLimit + if !isOurCommit { + dustLimit = chanState.RemoteChanCfg.DustLimit + } + // The "local" and "remote" notations are always from the perspective of // the local node. So if we want to find out the asset balance of the // _initiator_ of the channel, we just need to take into account the @@ -707,6 +712,19 @@ func CreateAllocations(chanState *channeldb.OpenChannel, ourBalance, allocType = CommitAllocationHtlcIncoming } + // If HTLC is dust, do not create allocation for it. + isDust := lnwallet.HtlcIsDust( + chanState.ChanType, isIncoming, isOurCommit, + filteredView.FeePerKw, htlc.Amount.ToSatoshis(), + dustLimit, + ) + if isDust { + // We need to error out, as a dust HTLC carrying assets + // should not be expected. + return fmt.Errorf("error creating asset HTLC " + + "allocation, HTLC is dust") + } + allocations = append(allocations, &Allocation{ Type: allocType, Amount: rfqmsg.Sum(htlc.AssetBalances), @@ -773,6 +791,16 @@ func CreateAllocations(chanState *channeldb.OpenChannel, ourBalance, "sibling: %w", err) } + // If HTLC is dust, do not create allocation for it. + isDust := lnwallet.HtlcIsDust( + chanState.ChanType, isIncoming, isOurCommit, + filteredView.FeePerKw, htlc.Amount.ToSatoshis(), + dustLimit, + ) + if isDust { + return nil + } + allocations = append(allocations, &Allocation{ Type: AllocationTypeNoAssets, BtcAmount: htlc.Amount.ToSatoshis(),