Skip to content
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
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