diff --git a/docs/examples/basic-price-oracle/go.mod b/docs/examples/basic-price-oracle/go.mod index 3e00eca50..64b3ff561 100644 --- a/docs/examples/basic-price-oracle/go.mod +++ b/docs/examples/basic-price-oracle/go.mod @@ -98,7 +98,7 @@ require ( github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd // indirect github.com/lightninglabs/neutrino/cache v1.1.2 // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb // indirect - github.com/lightningnetwork/lnd v0.18.4-beta.rc1 // indirect + github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5 // indirect github.com/lightningnetwork/lnd/clock v1.1.1 // indirect github.com/lightningnetwork/lnd/fn v1.2.3 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.5 // indirect diff --git a/docs/examples/basic-price-oracle/go.sum b/docs/examples/basic-price-oracle/go.sum index f10f531c0..09b036ebc 100644 --- a/docs/examples/basic-price-oracle/go.sum +++ b/docs/examples/basic-price-oracle/go.sum @@ -432,8 +432,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.4-beta.rc1 h1:z6hFKvtbfo8udPrIb81GbSoKlUWd06d4LRxTkD19IMQ= -github.com/lightningnetwork/lnd v0.18.4-beta.rc1/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= +github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5 h1:YJ/DPJd3YyPWmvv5b74KdpCFi3uBMdmDei54AageGpc= +github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn v1.2.3 h1:Q1OrgNSgQynVheBNa16CsKVov1JI5N2AR6G07x9Mles= diff --git a/go.mod b/go.mod index b69c43d06..798befaf6 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 github.com/lightninglabs/lndclient v0.18.4-7 github.com/lightninglabs/neutrino/cache v1.1.2 - github.com/lightningnetwork/lnd v0.18.4-beta.rc1 + github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5 github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 github.com/lightningnetwork/lnd/fn v1.2.3 diff --git a/go.sum b/go.sum index cbc79d670..8d828d4d7 100644 --- a/go.sum +++ b/go.sum @@ -500,8 +500,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.4-beta.rc1 h1:z6hFKvtbfo8udPrIb81GbSoKlUWd06d4LRxTkD19IMQ= -github.com/lightningnetwork/lnd v0.18.4-beta.rc1/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= +github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5 h1:YJ/DPJd3YyPWmvv5b74KdpCFi3uBMdmDei54AageGpc= +github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= 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/rpcserver.go b/rpcserver.go index 38ab2640f..140f9bb61 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6819,13 +6819,9 @@ func marshallRfqEvent(eventInterface fn.Event) (*rfqrpc.RfqEvent, error) { }, nil case *rfq.PeerAcceptedSellQuoteEvent: - rpcAcceptedQuote, err := taprpc.MarshalAcceptedSellQuoteEvent( + rpcAcceptedQuote := taprpc.MarshalAcceptedSellQuoteEvent( event, ) - if err != nil { - return nil, fmt.Errorf("error marshalling accepted "+ - "sell quote event: %w", err) - } eventRpc := &rfqrpc.RfqEvent_PeerAcceptedSellQuote{ PeerAcceptedSellQuote: &rfqrpc.PeerAcceptedSellQuoteEvent{ @@ -7036,9 +7032,9 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, // Continue below. case req.RfqId != nil: - // Check if the provided rfq ID matches the expected length. + // Check if the provided RFQ ID matches the expected length. if len(req.RfqId) != 32 { - return fmt.Errorf("rfq must be 32 bytes in length") + return fmt.Errorf("RFQ ID must be 32 bytes in length") } // Now let's try to perform an internal lookup to see if there's @@ -7061,36 +7057,12 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, "accepted quote") } - invoice, err := zpay32.Decode( - pReq.PaymentRequest, r.cfg.Lnd.ChainParams, - ) - if err != nil { - return fmt.Errorf("error decoding payment request: %w", - err) - } - - rate := quote.AssetRate.Rate - // Calculate the equivalent asset units for the given invoice // amount based on the asset-to-BTC conversion rate. - numAssetUnits := rfqmath.MilliSatoshiToUnits( - *invoice.MilliSat, rate, - ) - - sellOrder := &rfqrpc.PeerAcceptedSellQuote{ - Peer: quote.Peer.String(), - Id: quote.ID[:], - Scid: uint64(quote.ID.Scid()), - BidAssetRate: &rfqrpc.FixedPoint{ - Coefficient: rate.Coefficient.String(), - Scale: uint32(rate.Scale), - }, - AssetAmount: numAssetUnits.ToUint64(), - Expiry: uint64(quote.AssetRate.Expiry.Unix()), - } + sellOrder := taprpc.MarshalAcceptedSellQuote(*quote) // Send out the information about the quote on the stream. - err = stream.Send(&tchrpc.SendPaymentResponse{ + err := stream.Send(&tchrpc.SendPaymentResponse{ Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{ AcceptedSellOrder: sellOrder, }, @@ -7101,8 +7073,8 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, } rpcsLog.Infof("Using quote for %v asset units at %v asset/BTC "+ - "from peer %x with SCID %d", numAssetUnits, - rate.String(), quote.Peer, quote.ID.Scid()) + "from peer %x with SCID %d", sellOrder.AssetAmount, + quote.AssetRate.String(), quote.Peer, quote.ID.Scid()) htlc := rfqmsg.NewHtlc(nil, fn.Some(quote.ID)) @@ -7243,15 +7215,9 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, err) } - // Calculate the equivalent asset units for the given invoice - // amount based on the asset-to-BTC conversion rate. - numAssetUnits := rfqmath.MilliSatoshiToUnits( - *invoice.MilliSat, *assetRate, - ) - rpcsLog.Infof("Got quote for %v asset units at %v asset/BTC "+ - "from peer %x with SCID %d", numAssetUnits, assetRate, - peerPubKey, acceptedQuote.Scid) + "from peer %x with SCID %d", acceptedQuote.AssetAmount, + assetRate, peerPubKey, acceptedQuote.Scid) var rfqID rfqmsg.ID copy(rfqID[:], acceptedQuote.Id) diff --git a/server.go b/server.go index 7daa8e4d7..dcd16e32e 100644 --- a/server.go +++ b/server.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" lfn "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/funding" + "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -37,7 +38,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/msgmux" - "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/tlv" "google.golang.org/grpc" @@ -696,14 +696,14 @@ func (s *Server) Stop() error { // A compile-time check to ensure that Server fully implements the // lnwallet.AuxLeafStore, lnd.AuxDataParser, lnwallet.AuxSigner, -// msgmux.Endpoint, funding.AuxFundingController, routing.TlvTrafficShaper +// msgmux.Endpoint, funding.AuxFundingController, htlcswitch.AuxTrafficShaper // and chancloser.AuxChanCloser interfaces. var _ lnwl.AuxLeafStore = (*Server)(nil) var _ lnd.AuxDataParser = (*Server)(nil) var _ lnwl.AuxSigner = (*Server)(nil) var _ msgmux.Endpoint = (*Server)(nil) var _ funding.AuxFundingController = (*Server)(nil) -var _ routing.TlvTrafficShaper = (*Server)(nil) +var _ htlcswitch.AuxTrafficShaper = (*Server)(nil) var _ chancloser.AuxChanCloser = (*Server)(nil) var _ lnwl.AuxContractResolver = (*Server)(nil) var _ sweep.AuxSweeper = (*Server)(nil) diff --git a/tapchannel/aux_traffic_shaper.go b/tapchannel/aux_traffic_shaper.go index 1974f58ed..5de385d5d 100644 --- a/tapchannel/aux_traffic_shaper.go +++ b/tapchannel/aux_traffic_shaper.go @@ -12,10 +12,10 @@ import ( "github.com/lightninglabs/taproot-assets/rfqmsg" cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg" lfn "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/tlv" ) @@ -84,8 +84,8 @@ func (s *AuxTrafficShaper) Stop() error { } // A compile-time check to ensure that AuxTrafficShaper fully implements the -// routing.TlvTrafficShaper interface. -var _ routing.TlvTrafficShaper = (*AuxTrafficShaper)(nil) +// htlcswitch.AuxTrafficShaper interface. +var _ htlcswitch.AuxTrafficShaper = (*AuxTrafficShaper)(nil) // ShouldHandleTraffic is called in order to check if the channel identified by // the provided channel ID is handled by the traffic shaper implementation. If @@ -187,8 +187,8 @@ func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, 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. + // it goes below the reserve, we report zero bandwidth as we + // cannot push the HTLC amount. if linkBandwidth < htlcAmt { return 0, nil } @@ -213,9 +213,21 @@ func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, // up the accepted quote and determine the outgoing bandwidth in // satoshis based on the local asset balance. rfqID := htlc.RfqID.ValOpt().UnsafeFromSome() - acceptedQuotes := s.cfg.RfqManager.PeerAcceptedSellQuotes() - quote, ok := acceptedQuotes[rfqID.Scid()] - if !ok { + acceptedSellQuotes := s.cfg.RfqManager.PeerAcceptedSellQuotes() + acceptedBuyQuotes := s.cfg.RfqManager.LocalAcceptedBuyQuotes() + + sellQuote, isSellQuote := acceptedSellQuotes[rfqID.Scid()] + buyQuote, isBuyQuote := acceptedBuyQuotes[rfqID.Scid()] + + var rate rfqmsg.AssetRate + switch { + case isSellQuote: + rate = sellQuote.AssetRate + + case isBuyQuote: + rate = buyQuote.AssetRate + + default: return 0, fmt.Errorf("no accepted quote found for RFQ ID "+ "%x (SCID %d)", rfqID[:], rfqID.Scid()) } @@ -224,7 +236,7 @@ func (s *AuxTrafficShaper) PaymentBandwidth(htlcBlob, // expressed in milli-satoshis. localBalanceFp := rfqmath.NewBigIntFixedPoint(localBalance, 0) availableBalanceMsat := rfqmath.UnitsToMilliSatoshi( - localBalanceFp, quote.AssetRate.Rate, + localBalanceFp, rate.Rate, ) // At this point we have acquired what we need to express the asset diff --git a/tapchannel/commitment.go b/tapchannel/commitment.go index e8bf200ff..1a714c7da 100644 --- a/tapchannel/commitment.go +++ b/tapchannel/commitment.go @@ -363,9 +363,10 @@ func processAddEntry(htlc *DecodedDescriptor, ourBalance, theirBalance uint64, // non-dust satoshi balance. It also checks and returns whether we need a local // and/or remote anchor output. func SanityCheckAmounts(ourBalance, theirBalance btcutil.Amount, - ourAssetBalance, theirAssetBalance uint64, view *DecodedView, - chanType channeldb.ChannelType, whoseCommit lntypes.ChannelParty, - dustLimit btcutil.Amount) (bool, bool, error) { + ourAssetBalance, theirAssetBalance uint64, assetView, + nonAssetView *DecodedView, chanType channeldb.ChannelType, + whoseCommit lntypes.ChannelParty, dustLimit btcutil.Amount) (bool, bool, + error) { log.Tracef("Sanity checking amounts, whoseCommit=%v, ourBalance=%d, "+ "theirBalance=%d, ourAssetBalance=%d, theirAssetBalance=%d", @@ -373,10 +374,36 @@ func SanityCheckAmounts(ourBalance, theirBalance btcutil.Amount, theirAssetBalance) var ( - numHTLCs int64 - feePerKw = view.FeePerKw + numHTLCs uint64 + feePerKw = assetView.FeePerKw ) - for _, entry := range view.OurUpdates { + + // We need to count any non-dust BTC-only HTLCs too for determining + // whether we need a commitment anchor output. The assetView and + // nonAssetView are non-overlapping, so we can just sum the number of + // non-dust HTLCs in both. + for _, entry := range nonAssetView.OurUpdates { + if !lnwallet.HtlcIsDust( + chanType, false, whoseCommit, feePerKw, + entry.Amount.ToSatoshis(), dustLimit, + ) { + + numHTLCs++ + } + } + for _, entry := range nonAssetView.TheirUpdates { + if !lnwallet.HtlcIsDust( + chanType, true, whoseCommit, feePerKw, + entry.Amount.ToSatoshis(), dustLimit, + ) { + + numHTLCs++ + } + } + + // And finally we check the asset HTLCs. Here we also enforce that an + // HTLC that's carrying an asset must be above dust. + for _, entry := range assetView.OurUpdates { isDust := lnwallet.HtlcIsDust( chanType, false, whoseCommit, feePerKw, entry.Amount.ToSatoshis(), dustLimit, @@ -391,7 +418,7 @@ func SanityCheckAmounts(ourBalance, theirBalance btcutil.Amount, numHTLCs++ } - for _, entry := range view.TheirUpdates { + for _, entry := range assetView.TheirUpdates { isDust := lnwallet.HtlcIsDust( chanType, true, whoseCommit, feePerKw, entry.Amount.ToSatoshis(), dustLimit, @@ -488,7 +515,7 @@ func GenerateCommitmentAllocations(prevState *cmsg.Commitment, // corresponding non-dust BTC output. wantLocalAnchor, wantRemoteAnchor, err := SanityCheckAmounts( ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), - ourAssetBalance, theirAssetBalance, filteredView, + ourAssetBalance, theirAssetBalance, filteredView, nonAssetView, chanState.ChanType, whoseCommit, dustLimit, ) if err != nil { diff --git a/taprpc/marshal.go b/taprpc/marshal.go index 7e36d9448..277db59c8 100644 --- a/taprpc/marshal.go +++ b/taprpc/marshal.go @@ -15,6 +15,8 @@ import ( "github.com/lightninglabs/taproot-assets/commitment" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/rfq" + "github.com/lightninglabs/taproot-assets/rfqmath" + "github.com/lightninglabs/taproot-assets/rfqmsg" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightningnetwork/lnd/keychain" ) @@ -522,25 +524,37 @@ func MarshalAsset(ctx context.Context, a *asset.Asset, } // MarshalAcceptedSellQuoteEvent marshals a peer accepted sell quote event to -// its rpc representation. +// its RPC representation. func MarshalAcceptedSellQuoteEvent( - event *rfq.PeerAcceptedSellQuoteEvent) (*rfqrpc.PeerAcceptedSellQuote, - error) { + event *rfq.PeerAcceptedSellQuoteEvent) *rfqrpc.PeerAcceptedSellQuote { + + return MarshalAcceptedSellQuote(event.SellAccept) +} + +// MarshalAcceptedSellQuote marshals a peer accepted sell quote to its RPC +// representation. +func MarshalAcceptedSellQuote( + accept rfqmsg.SellAccept) *rfqrpc.PeerAcceptedSellQuote { rpcAssetRate := &rfqrpc.FixedPoint{ - Coefficient: event.AssetRate.Rate.Coefficient.String(), - Scale: uint32(event.AssetRate.Rate.Scale), + Coefficient: accept.AssetRate.Rate.Coefficient.String(), + Scale: uint32(accept.AssetRate.Rate.Scale), } - // TODO(ffranr): Add SellRequest payment max amount to - // PeerAcceptedSellQuote. + // Calculate the equivalent asset units for the given total BTC amount + // based on the asset-to-BTC conversion rate. + numAssetUnits := rfqmath.MilliSatoshiToUnits( + accept.Request.PaymentMaxAmt, accept.AssetRate.Rate, + ) + return &rfqrpc.PeerAcceptedSellQuote{ - Peer: event.Peer.String(), - Id: event.ID[:], - Scid: uint64(event.ShortChannelId()), + Peer: accept.Peer.String(), + Id: accept.ID[:], + Scid: uint64(accept.ShortChannelId()), BidAssetRate: rpcAssetRate, - Expiry: uint64(event.AssetRate.Expiry.Unix()), - }, nil + Expiry: uint64(accept.AssetRate.Expiry.Unix()), + AssetAmount: numAssetUnits.ScaleTo(0).ToUint64(), + } } // MarshalAcceptedBuyQuoteEvent marshals a peer accepted buy quote event to @@ -636,14 +650,8 @@ func NewAddAssetSellOrderResponse( switch e := event.(type) { case *rfq.PeerAcceptedSellQuoteEvent: - rpcAcceptedQuote, err := MarshalAcceptedSellQuoteEvent(e) - if err != nil { - return nil, fmt.Errorf("unable to marshal accepted "+ - "sell quote event to RPC: %w", err) - } - resp.Response = &rfqrpc.AddAssetSellOrderResponse_AcceptedQuote{ - AcceptedQuote: rpcAcceptedQuote, + AcceptedQuote: MarshalAcceptedSellQuoteEvent(e), } return resp, nil