Skip to content

Commit

Permalink
Merge pull request #845 from lightninglabs/rfq-refactor-for-sell-support
Browse files Browse the repository at this point in the history
RFQ refactor towards adding asset sell request support
  • Loading branch information
ffranr authored Mar 21, 2024
2 parents 7863415 + 13dddf3 commit 1656733
Show file tree
Hide file tree
Showing 18 changed files with 464 additions and 405 deletions.
26 changes: 15 additions & 11 deletions itest/rfq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ import (
"github.com/stretchr/testify/require"
)

// testRfqHtlcIntercept tests RFQ negotiation and HTLC interception and
// validation between three peers.
// testRfqAssetBuyHtlcIntercept tests RFQ negotiation, HTLC interception, and
// validation between three peers. The RFQ negotiation is initiated by an asset
// buy request.
//
// The procedure is as follows:
// 1. Carol sends a tap asset request for quote (buy order) to Bob.
// 1. Carol sends a tap asset buy quote request to Bob.
// 2. Bob's node accepts the quote.
// 3. Carol uses the quote accept message to construct a lightning invoice which
// will pay for the quote accepted by Bob.
// 3. Carol uses the buy accept message to construct a lightning invoice
// which will pay for the quote accepted by Bob.
// 4. Alice pays the invoice.
// 5. Bob's node intercepts the lightning payment from Alice and validates it
// against the quote accepted between Bob and Carol.
func testRfqHtlcIntercept(t *harnessTest) {
//
// As a final step (which is not part of this test), Bob's node will transfer
// the tap asset to Carol's node.
func testRfqAssetBuyHtlcIntercept(t *harnessTest) {
// Initialize a new test scenario.
ts := newRfqTestScenario(t)

Expand Down Expand Up @@ -98,7 +102,7 @@ func testRfqHtlcIntercept(t *harnessTest) {
event, err := carolEventNtfns.Recv()
require.NoError(t.t, err)

_, ok := event.Event.(*rfqrpc.RfqEvent_IncomingAcceptQuote)
_, ok := event.Event.(*rfqrpc.RfqEvent_PeerAcceptedBuyQuote)
require.True(t.t, ok, "unexpected event: %v", event)

return nil
Expand All @@ -107,11 +111,11 @@ func testRfqHtlcIntercept(t *harnessTest) {

// Carol should have received an accepted quote from Bob. This accepted
// quote can be used by Carol to make a payment to Bob.
acceptedQuotes, err := ts.CarolTapd.QueryRfqAcceptedQuotes(
ctxt, &rfqrpc.QueryRfqAcceptedQuotesRequest{},
acceptedQuotes, err := ts.CarolTapd.QueryPeerAcceptedQuotes(
ctxt, &rfqrpc.QueryPeerAcceptedQuotesRequest{},
)
require.NoError(t.t, err, "unable to query accepted quotes")
require.Len(t.t, acceptedQuotes.AcceptedQuotes, 1)
require.Len(t.t, acceptedQuotes.BuyQuotes, 1)

// Carol will now use the accepted quote (received from Bob) to create
// a lightning invoice which will be given to and settled by Alice.
Expand All @@ -126,7 +130,7 @@ func testRfqHtlcIntercept(t *harnessTest) {
// pays the invoice, the payment will arrive to Bob's node with the
// expected scid. Bob will then use the scid to identify the HTLC as
// relating to the accepted quote.
acceptedQuote := acceptedQuotes.AcceptedQuotes[0]
acceptedQuote := acceptedQuotes.BuyQuotes[0]
t.Logf("Accepted quote scid: %d", acceptedQuote.Scid)
scid := lnwire.NewShortChanIDFromInt(acceptedQuote.Scid)

Expand Down
4 changes: 2 additions & 2 deletions itest/test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ var testCases = []*testCase{

// Request for quote (RFQ) tests.
{
name: "rfq htlc intercept",
test: testRfqHtlcIntercept,
name: "rfq asset buy htlc intercept",
test: testRfqAssetBuyHtlcIntercept,
},
{
name: "multi signature on all levels",
Expand Down
2 changes: 1 addition & 1 deletion perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ var (
Entity: "rfq",
Action: "write",
}},
"/rfqrpc.Rfq/QueryRfqAcceptedQuotes": {{
"/rfqrpc.Rfq/QueryPeerAcceptedQuotes": {{
Entity: "rfq",
Action: "read",
}},
Expand Down
67 changes: 35 additions & 32 deletions rfq/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ type Manager struct {
// events.
acceptHtlcEvents chan *AcceptHtlcEvent

// peerAcceptedQuotes is a map of serialised short channel IDs (SCIDs)
// to associated accepted quotes. These quotes have been accepted by
// peer nodes and are therefore available for use in buying assets.
peerAcceptedQuotes lnutils.SyncMap[SerialisedScid, rfqmsg.BuyAccept]
// peerAcceptedBuyQuotes holds buy quotes for assets that our node has
// requested and that have been accepted 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.
peerAcceptedBuyQuotes lnutils.SyncMap[SerialisedScid, rfqmsg.BuyAccept]

// subscribers is a map of components that want to be notified on new
// events, keyed by their subscription ID.
Expand All @@ -104,7 +105,7 @@ func NewManager(cfg ManagerCfg) (*Manager, error) {
outgoingMessages: make(chan rfqmsg.OutgoingMsg),

acceptHtlcEvents: make(chan *AcceptHtlcEvent),
peerAcceptedQuotes: lnutils.SyncMap[
peerAcceptedBuyQuotes: lnutils.SyncMap[
SerialisedScid, rfqmsg.BuyAccept]{},

subscribers: lnutils.SyncMap[
Expand Down Expand Up @@ -273,10 +274,11 @@ func (m *Manager) handleIncomingMessage(incomingMsg rfqmsg.IncomingMsg) error {
// so that it can be used to send a payment by our lightning
// node.
scid := SerialisedScid(msg.ShortChannelId())
m.peerAcceptedQuotes.Store(scid, *msg)
m.peerAcceptedBuyQuotes.Store(scid, *msg)

// Notify subscribers of the incoming quote accept.
event := NewIncomingAcceptQuoteEvent(msg)
// Notify subscribers of the incoming peer accepted asset buy
// quote.
event := NewPeerAcceptedBuyQuoteEvent(msg)
m.publishSubscriberEvent(event)

case *rfqmsg.Reject:
Expand Down Expand Up @@ -425,17 +427,18 @@ func (m *Manager) UpsertAssetBuyOrder(order BuyOrder) error {
return nil
}

// QueryAcceptedQuotes returns a map of accepted quotes that have been
// registered with the RFQ manager.
func (m *Manager) QueryAcceptedQuotes() map[SerialisedScid]rfqmsg.BuyAccept {
// QueryPeerAcceptedQuotes returns quotes that were requested by our node and
// have been accepted by our peers. These quotes are exclusively available to
// our node for the acquisition/sale of assets.
func (m *Manager) QueryPeerAcceptedQuotes() map[SerialisedScid]rfqmsg.BuyAccept {
// Returning the map directly is not thread safe. We will therefore
// create a copy.
quotesCopy := make(map[SerialisedScid]rfqmsg.BuyAccept)

m.peerAcceptedQuotes.ForEach(
m.peerAcceptedBuyQuotes.ForEach(
func(scid SerialisedScid, accept rfqmsg.BuyAccept) error {
if time.Now().Unix() > int64(accept.Expiry) {
m.peerAcceptedQuotes.Delete(scid)
m.peerAcceptedBuyQuotes.Delete(scid)
return nil
}

Expand Down Expand Up @@ -487,34 +490,34 @@ func (m *Manager) publishSubscriberEvent(event fn.Event) {
)
}

// IncomingAcceptQuoteEvent is an event that is broadcast when the RFQ manager
// receives an accept quote message from a peer.
type IncomingAcceptQuoteEvent struct {
// PeerAcceptedBuyQuoteEvent is an event that is broadcast when the RFQ manager
// receives an accept quote message from a peer. This is a quote which was
// requested by our node and has been accepted by a peer.
type PeerAcceptedBuyQuoteEvent struct {
// timestamp is the event creation UTC timestamp.
timestamp time.Time

// BuyAccept is the accepted quote.
// BuyAccept is the accepted asset buy quote.
rfqmsg.BuyAccept
}

// NewIncomingAcceptQuoteEvent creates a new IncomingAcceptQuoteEvent.
func NewIncomingAcceptQuoteEvent(
accept *rfqmsg.BuyAccept) *IncomingAcceptQuoteEvent {
// NewPeerAcceptedBuyQuoteEvent creates a new PeerAcceptedBuyQuoteEvent.
func NewPeerAcceptedBuyQuoteEvent(
buyAccept *rfqmsg.BuyAccept) *PeerAcceptedBuyQuoteEvent {

return &IncomingAcceptQuoteEvent{
return &PeerAcceptedBuyQuoteEvent{
timestamp: time.Now().UTC(),
BuyAccept: *accept,
BuyAccept: *buyAccept,
}
}

// Timestamp returns the event creation UTC timestamp.
func (q *IncomingAcceptQuoteEvent) Timestamp() time.Time {
func (q *PeerAcceptedBuyQuoteEvent) Timestamp() time.Time {
return q.timestamp.UTC()
}

// Ensure that the IncomingAcceptQuoteEvent struct implements the Event
// interface.
var _ fn.Event = (*IncomingAcceptQuoteEvent)(nil)
// Ensure that the PeerAcceptedBuyQuoteEvent struct implements the Event interface.
var _ fn.Event = (*PeerAcceptedBuyQuoteEvent)(nil)

// IncomingRejectQuoteEvent is an event that is broadcast when the RFQ manager
// receives a reject quote message from a peer.
Expand Down Expand Up @@ -554,18 +557,18 @@ type AcceptHtlcEvent struct {
// Htlc is the intercepted HTLC.
Htlc lndclient.InterceptedHtlc

// ChannelRemit is the channel remit that the HTLC complies with.
ChannelRemit ChannelRemit
// Policy is the policy with which the HTLC is compliant.
Policy Policy
}

// NewAcceptHtlcEvent creates a new AcceptedHtlcEvent.
func NewAcceptHtlcEvent(htlc lndclient.InterceptedHtlc,
channelRemit ChannelRemit) *AcceptHtlcEvent {
policy Policy) *AcceptHtlcEvent {

return &AcceptHtlcEvent{
timestamp: uint64(time.Now().UTC().Unix()),
Htlc: htlc,
ChannelRemit: channelRemit,
timestamp: uint64(time.Now().UTC().Unix()),
Htlc: htlc,
Policy: policy,
}
}

Expand Down
5 changes: 3 additions & 2 deletions rfq/negotiator.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (n *Negotiator) HandleIncomingBuyRequest(
// If we do not have a suitable sell offer, then we will reject
// the quote request with an error.
reject := rfqmsg.NewReject(
request, rfqmsg.ErrNoSuitableSellOffer,
request.Peer, request.ID, rfqmsg.ErrNoSuitableSellOffer,
)
var msg rfqmsg.OutgoingMsg = reject

Expand Down Expand Up @@ -271,7 +271,8 @@ func (n *Negotiator) HandleIncomingBuyRequest(
if err != nil {
// Send a reject message to the peer.
msg := rfqmsg.NewReject(
request, rfqmsg.ErrUnknownReject,
request.Peer, request.ID,
rfqmsg.ErrUnknownReject,
)
sendOutgoingMsg(msg)

Expand Down
7 changes: 6 additions & 1 deletion rfq/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,13 @@ func (m *MockPriceOracle) QueryAskPrice(_ context.Context,
// Calculate the rate expiryDelay lifetime.
expiry := uint64(time.Now().Unix()) + m.expiryDelay

askPrice := lnwire.MilliSatoshi(42000)
if suggestedBidPrice != nil {
askPrice = *suggestedBidPrice
}

return &OracleAskResponse{
AskPrice: suggestedBidPrice,
AskPrice: &askPrice,
Expiry: expiry,
}, nil
}
Expand Down
Loading

0 comments on commit 1656733

Please sign in to comment.