Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rfq+tapchannel: track locally accepted quotes in manager #928

Merged
merged 1 commit into from
Jun 6, 2024
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
78 changes: 74 additions & 4 deletions rfq/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,23 @@ type Manager struct {
// requested and that have been accepted by peer nodes. These quotes are
// exclusively used by our node for the sale of assets, as they
// represent agreed-upon terms for sale transactions with our peers.
peerAcceptedSellQuotes lnutils.SyncMap[SerialisedScid,
rfqmsg.SellAccept]
peerAcceptedSellQuotes lnutils.SyncMap[
SerialisedScid, rfqmsg.SellAccept,
]

// localAcceptedBuyQuotes holds buy quotes for assets that our node has
// accepted and that have been requested by peer nodes. These quotes are
// exclusively used by our node for the acquisition of assets, as they
// represent agreed-upon terms for purchase transactions with our peers.
localAcceptedBuyQuotes lnutils.SyncMap[SerialisedScid, rfqmsg.BuyAccept]

// localAcceptedSellQuotes holds sell quotes for assets that our node
// has accepted and that have been requested by peer nodes. These quotes
// are exclusively used by our node for the sale of assets, as they
// represent agreed-upon terms for sale transactions with our peers.
localAcceptedSellQuotes lnutils.SyncMap[
SerialisedScid, rfqmsg.SellAccept,
]

// subscribers is a map of components that want to be notified on new
// events, keyed by their subscription ID.
Expand Down Expand Up @@ -427,6 +442,10 @@ func (m *Manager) handleOutgoingMessage(outgoingMsg rfqmsg.OutgoingMsg) error {
// sale policy.
m.orderHandler.RegisterAssetSalePolicy(*msg)

// We want to store that we accepted the buy quote, in case we
// need to look it up for a direct peer payment.
m.localAcceptedBuyQuotes.Store(msg.ShortChannelId(), *msg)

// Since our peer is going to buy assets from us, we need to
// make sure we can identify the forwarded asset payment by the
// outgoing SCID alias within the onion packet.
Expand All @@ -444,6 +463,10 @@ func (m *Manager) handleOutgoingMessage(outgoingMsg rfqmsg.OutgoingMsg) error {
// handler that we are willing to buy the asset subject to a
// purchase policy.
m.orderHandler.RegisterAssetPurchasePolicy(*msg)

// We want to store that we accepted the sell quote, in case we
// need to look it up for a direct peer payment.
m.localAcceptedSellQuotes.Store(msg.ShortChannelId(), *msg)
}

// Send the outgoing message to the peer.
Expand Down Expand Up @@ -713,15 +736,62 @@ func (m *Manager) PeerAcceptedBuyQuotes() map[SerialisedScid]rfqmsg.BuyAccept {
// PeerAcceptedSellQuotes returns sell quotes that were requested by our node
// and have been accepted by our peers. These quotes are exclusively available
// to our node for the sale of assets.
// nolint: lll
//
//nolint:lll
func (m *Manager) PeerAcceptedSellQuotes() map[SerialisedScid]rfqmsg.SellAccept {
// Returning the map directly is not thread safe. We will therefore
// create a copy.
sellQuotesCopy := make(map[SerialisedScid]rfqmsg.SellAccept)
m.peerAcceptedSellQuotes.ForEach(
func(scid SerialisedScid, accept rfqmsg.SellAccept) error {
if time.Now().Unix() > int64(accept.Expiry) {
m.peerAcceptedBuyQuotes.Delete(scid)
m.peerAcceptedSellQuotes.Delete(scid)
return nil
}

sellQuotesCopy[scid] = accept
return nil
},
)

return sellQuotesCopy
}

// LocalAcceptedBuyQuotes returns buy quotes that were accepted by our node and
// have been requested by our peers. These quotes are exclusively available to
// our node for the acquisition of assets.
func (m *Manager) LocalAcceptedBuyQuotes() map[SerialisedScid]rfqmsg.BuyAccept {
// Returning the map directly is not thread safe. We will therefore
// create a copy.
buyQuotesCopy := make(map[SerialisedScid]rfqmsg.BuyAccept)
m.localAcceptedBuyQuotes.ForEach(
func(scid SerialisedScid, accept rfqmsg.BuyAccept) error {
if time.Now().Unix() > int64(accept.Expiry) {
m.localAcceptedBuyQuotes.Delete(scid)
return nil
}

buyQuotesCopy[scid] = accept
return nil
},
)

return buyQuotesCopy
}

// LocalAcceptedSellQuotes returns sell quotes that were accepted by our node
// and have been requested by our peers. These quotes are exclusively available
// to our node for the sale of assets.
//
//nolint:lll
func (m *Manager) LocalAcceptedSellQuotes() map[SerialisedScid]rfqmsg.SellAccept {
// Returning the map directly is not thread safe. We will therefore
// create a copy.
sellQuotesCopy := make(map[SerialisedScid]rfqmsg.SellAccept)
m.localAcceptedSellQuotes.ForEach(
func(scid SerialisedScid, accept rfqmsg.SellAccept) error {
if time.Now().Unix() > int64(accept.Expiry) {
m.localAcceptedSellQuotes.Delete(scid)
return nil
}

Expand Down
50 changes: 42 additions & 8 deletions tapchannel/aux_invoice_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sync"

"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/taproot-assets/address"
"github.com/lightninglabs/taproot-assets/fn"
Expand Down Expand Up @@ -143,16 +144,12 @@ func (s *AuxInvoiceManager) handleInvoiceAccept(_ context.Context,
}

rfqID := htlc.RfqID.ValOpt().UnsafeFromSome()
acceptedQuotes := s.cfg.RfqManager.PeerAcceptedBuyQuotes()
quote, ok := acceptedQuotes[rfqID.Scid()]
if !ok {
return nil, fmt.Errorf("no accepted quote found for RFQ SCID "+
"%d", rfqID.Scid())
mSatPerAssetUnit, err := s.priceFromQuote(rfqID)
if err != nil {
return nil, fmt.Errorf("unable to get price from quote with "+
"ID %x / SCID %d: %w", rfqID[:], rfqID.Scid(), err)
}

log.Debugf("Found quote for SCID %d: %#v", rfqID.Scid(), quote)

mSatPerAssetUnit := quote.AskPrice
htlcAssetAmount := lnwire.MilliSatoshi(htlc.Amounts.Val.Sum())
resp.AmtPaid = htlcAssetAmount * mSatPerAssetUnit

Expand Down Expand Up @@ -193,6 +190,43 @@ func (s *AuxInvoiceManager) handleInvoiceAccept(_ context.Context,
return resp, nil
}

// priceFromQuote retrieves the price from the accepted quote for the given RFQ
// ID. We allow the quote to either be a buy or a sell quote, since we don't
// know if this is a direct peer payment or a payment that is routed through the
// multiple hops. If it's a direct peer payment, then the quote will be a sell
// quote, since that's what the peer created to find out how many units to send
// for an invoice denominated in BTC.
func (s *AuxInvoiceManager) priceFromQuote(rfqID rfqmsg.ID) (
lnwire.MilliSatoshi, error) {

acceptedBuyQuotes := s.cfg.RfqManager.PeerAcceptedBuyQuotes()
acceptedSellQuotes := s.cfg.RfqManager.LocalAcceptedSellQuotes()

log.Tracef("Currently available quotes: buy %v, sell %v",
spew.Sdump(acceptedBuyQuotes), spew.Sdump(acceptedSellQuotes))

buyQuote, isBuy := acceptedBuyQuotes[rfqID.Scid()]
sellQuote, isSell := acceptedSellQuotes[rfqID.Scid()]

switch {
case isBuy:
log.Debugf("Found buy quote for ID %x / SCID %d: %#v",
rfqID[:], rfqID.Scid(), buyQuote)

return buyQuote.AskPrice, nil

case isSell:
log.Debugf("Found sell quote for ID %x / SCID %d: %#v",
rfqID[:], rfqID.Scid(), sellQuote)

return sellQuote.BidPrice, nil

default:
return 0, fmt.Errorf("no accepted quote found for RFQ SCID "+
"%d", rfqID.Scid())
}
}

// Stop signals for an aux invoice manager to gracefully exit.
func (s *AuxInvoiceManager) Stop() error {
var stopErr error
Expand Down
Loading