Skip to content

Commit 6c7797d

Browse files
committed
rfqmsg: add new RFQ message types package
This commit adds a new package called rfqmsg which contains RFQ message types. Message types are: request, accept, and reject.
1 parent 5169330 commit 6c7797d

File tree

6 files changed

+1048
-0
lines changed

6 files changed

+1048
-0
lines changed

rfqmsg/accept.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package rfqmsg
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"fmt"
7+
"io"
8+
9+
"github.com/lightningnetwork/lnd/lnwire"
10+
"github.com/lightningnetwork/lnd/routing/route"
11+
"github.com/lightningnetwork/lnd/tlv"
12+
)
13+
14+
const (
15+
// Accept message type field TLV types.
16+
17+
TypeAcceptID tlv.Type = 0
18+
TypeAcceptAskPrice tlv.Type = 2
19+
TypeAcceptExpiry tlv.Type = 4
20+
TypeAcceptSignature tlv.Type = 6
21+
)
22+
23+
func TypeRecordAcceptID(id *ID) tlv.Record {
24+
const recordSize = 32
25+
26+
return tlv.MakeStaticRecord(
27+
TypeAcceptID, id, recordSize, IdEncoder, IdDecoder,
28+
)
29+
}
30+
31+
func TypeRecordAcceptAskPrice(askPrice *lnwire.MilliSatoshi) tlv.Record {
32+
return tlv.MakeStaticRecord(
33+
TypeAcceptAskPrice, askPrice, 8, milliSatoshiEncoder,
34+
milliSatoshiDecoder,
35+
)
36+
}
37+
38+
func milliSatoshiEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
39+
if ms, ok := val.(*lnwire.MilliSatoshi); ok {
40+
msUint64 := uint64(*ms)
41+
return tlv.EUint64(w, &msUint64, buf)
42+
}
43+
44+
return tlv.NewTypeForEncodingErr(val, "MilliSatoshi")
45+
}
46+
47+
func milliSatoshiDecoder(r io.Reader, val interface{}, buf *[8]byte,
48+
l uint64) error {
49+
50+
if ms, ok := val.(*lnwire.MilliSatoshi); ok {
51+
var msInt uint64
52+
err := tlv.DUint64(r, &msInt, buf, l)
53+
if err != nil {
54+
return err
55+
}
56+
57+
*ms = lnwire.MilliSatoshi(msInt)
58+
return nil
59+
}
60+
61+
return tlv.NewTypeForDecodingErr(val, "MilliSatoshi", l, 8)
62+
}
63+
64+
func TypeRecordAcceptExpiry(expirySeconds *uint64) tlv.Record {
65+
return tlv.MakePrimitiveRecord(
66+
TypeAcceptExpiry, expirySeconds,
67+
)
68+
}
69+
70+
func TypeRecordAcceptSig(sig *[64]byte) tlv.Record {
71+
return tlv.MakePrimitiveRecord(
72+
TypeAcceptSignature, sig,
73+
)
74+
}
75+
76+
// acceptMsgData is a struct that represents the data field of a quote
77+
// accept message.
78+
type acceptMsgData struct {
79+
// ID represents the unique identifier of the quote request message that
80+
// this response is associated with.
81+
ID ID
82+
83+
// AskPrice is the asking price of the quote.
84+
AskPrice lnwire.MilliSatoshi
85+
86+
// Expiry is the asking price expiry lifetime unix timestamp.
87+
Expiry uint64
88+
89+
// sig is a signature over the serialized contents of the message.
90+
sig [64]byte
91+
}
92+
93+
// encodeRecords determines the non-nil records to include when encoding at
94+
// runtime.
95+
func (q *acceptMsgData) encodeRecords() []tlv.Record {
96+
var records []tlv.Record
97+
98+
// Add id record.
99+
records = append(records, TypeRecordAcceptID(&q.ID))
100+
101+
// Add ask price record.
102+
records = append(records, TypeRecordAcceptAskPrice(&q.AskPrice))
103+
104+
// Add expiry record.
105+
records = append(
106+
records, TypeRecordAcceptExpiry(&q.Expiry),
107+
)
108+
109+
// Add signature record.
110+
records = append(
111+
records, TypeRecordAcceptSig(&q.sig),
112+
)
113+
114+
return records
115+
}
116+
117+
// Encode encodes the structure into a TLV stream.
118+
func (q *acceptMsgData) Encode(writer io.Writer) error {
119+
stream, err := tlv.NewStream(q.encodeRecords()...)
120+
if err != nil {
121+
return err
122+
}
123+
return stream.Encode(writer)
124+
}
125+
126+
// DecodeRecords provides all TLV records for decoding.
127+
func (q *acceptMsgData) decodeRecords() []tlv.Record {
128+
return []tlv.Record{
129+
TypeRecordAcceptID(&q.ID),
130+
TypeRecordAcceptAskPrice(&q.AskPrice),
131+
TypeRecordAcceptExpiry(&q.Expiry),
132+
TypeRecordAcceptSig(&q.sig),
133+
}
134+
}
135+
136+
// Decode decodes the structure from a TLV stream.
137+
func (q *acceptMsgData) Decode(r io.Reader) error {
138+
stream, err := tlv.NewStream(q.decodeRecords()...)
139+
if err != nil {
140+
return err
141+
}
142+
return stream.DecodeP2P(r)
143+
}
144+
145+
// Bytes encodes the structure into a TLV stream and returns the bytes.
146+
func (q *acceptMsgData) Bytes() ([]byte, error) {
147+
var b bytes.Buffer
148+
err := q.Encode(&b)
149+
if err != nil {
150+
return nil, err
151+
}
152+
153+
return b.Bytes(), nil
154+
}
155+
156+
// Accept is a struct that represents an accepted quote message.
157+
type Accept struct {
158+
// Peer is the peer that sent the quote request.
159+
Peer route.Vertex
160+
161+
// AssetAmount is the amount of the asset that the accept message
162+
// is for.
163+
AssetAmount uint64
164+
165+
// acceptMsgData is the message data for the quote accept message.
166+
acceptMsgData
167+
}
168+
169+
// NewAcceptFromRequest creates a new instance of a quote accept message given
170+
// a quote request message.
171+
func NewAcceptFromRequest(request Request, askPrice lnwire.MilliSatoshi,
172+
expiry uint64) *Accept {
173+
174+
return &Accept{
175+
Peer: request.Peer,
176+
AssetAmount: request.AssetAmount,
177+
acceptMsgData: acceptMsgData{
178+
ID: request.ID,
179+
AskPrice: askPrice,
180+
Expiry: expiry,
181+
},
182+
}
183+
}
184+
185+
// NewAcceptFromWireMsg instantiates a new instance from a wire message.
186+
func NewAcceptFromWireMsg(wireMsg WireMessage) (*Accept, error) {
187+
// Ensure that the message type is an accept message.
188+
if wireMsg.MsgType != MsgTypeAccept {
189+
return nil, fmt.Errorf("unable to create an accept message "+
190+
"from wire message of type %d", wireMsg.MsgType)
191+
}
192+
193+
// Decode message data component from TLV bytes.
194+
var msgData acceptMsgData
195+
err := msgData.Decode(bytes.NewReader(wireMsg.Data))
196+
if err != nil {
197+
return nil, fmt.Errorf("unable to decode quote accept "+
198+
"message data: %w", err)
199+
}
200+
201+
return &Accept{
202+
Peer: wireMsg.Peer,
203+
acceptMsgData: msgData,
204+
}, nil
205+
}
206+
207+
// ShortChannelId returns the short channel ID of the quote accept.
208+
func (q *Accept) ShortChannelId() SerialisedScid {
209+
// Given valid RFQ message id, we then define a RFQ short chain id
210+
// (SCID) by taking the last 8 bytes of the RFQ message id and
211+
// interpreting them as a 64-bit integer.
212+
scidBytes := q.ID[24:]
213+
214+
scidInteger := binary.BigEndian.Uint64(scidBytes)
215+
return SerialisedScid(scidInteger)
216+
}
217+
218+
// ToWire returns a wire message with a serialized data field.
219+
//
220+
// TODO(ffranr): This method should accept a signer so that we can generate a
221+
// signature over the message data.
222+
func (q *Accept) ToWire() (WireMessage, error) {
223+
// Encode message data component as TLV bytes.
224+
msgDataBytes, err := q.acceptMsgData.Bytes()
225+
if err != nil {
226+
return WireMessage{}, fmt.Errorf("unable to encode message "+
227+
"data: %w", err)
228+
}
229+
230+
return WireMessage{
231+
Peer: q.Peer,
232+
MsgType: MsgTypeAccept,
233+
Data: msgDataBytes,
234+
}, nil
235+
}
236+
237+
// String returns a human-readable string representation of the message.
238+
func (q *Accept) String() string {
239+
return fmt.Sprintf("Accept(peer=%x, id=%x, ask_price=%d, expiry=%d)",
240+
q.Peer[:], q.ID, q.AskPrice, q.Expiry)
241+
}
242+
243+
// Ensure that the message type implements the OutgoingMsg interface.
244+
var _ OutgoingMsg = (*Accept)(nil)
245+
246+
// Ensure that the message type implements the IncomingMsg interface.
247+
var _ IncomingMsg = (*Accept)(nil)

rfqmsg/accept_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package rfqmsg
2+
3+
import (
4+
"encoding/binary"
5+
"math/rand"
6+
"testing"
7+
8+
"github.com/lightninglabs/taproot-assets/internal/test"
9+
"github.com/lightningnetwork/lnd/lnwire"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// TestAcceptShortChannelId tests the ShortChannelId method of a quote accept
14+
// message.
15+
func TestAcceptShortChannelId(t *testing.T) {
16+
t.Parallel()
17+
18+
// Generate a random short channel ID.
19+
scidInt := rand.Uint64()
20+
scid := lnwire.NewShortChanIDFromInt(scidInt)
21+
22+
// Create a random ID.
23+
randomIdBytes := test.RandBytes(32)
24+
id := ID(randomIdBytes)
25+
26+
// Set the last 8 bytes of the ID to the short channel ID.
27+
binary.BigEndian.PutUint64(id[24:], scid.ToUint64())
28+
29+
// Create an accept message.
30+
acceptMsg := Accept{
31+
acceptMsgData: acceptMsgData{
32+
ID: id,
33+
},
34+
}
35+
36+
// Derive the short channel ID from the accept message.
37+
actualScidInt := acceptMsg.ShortChannelId()
38+
39+
// Assert that the derived short channel ID is equal to the expected
40+
// short channel ID.
41+
require.Equal(t, scidInt, uint64(actualScidInt))
42+
}

rfqmsg/messages.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package rfqmsg
2+
3+
import (
4+
"encoding/hex"
5+
"errors"
6+
"math"
7+
8+
"github.com/lightningnetwork/lnd/lnwire"
9+
"github.com/lightningnetwork/lnd/routing/route"
10+
)
11+
12+
// ID is the identifier for a RFQ message.
13+
type ID [32]byte
14+
15+
// String returns the string representation of the ID.
16+
func (id ID) String() string {
17+
return hex.EncodeToString(id[:])
18+
}
19+
20+
// SerialisedScid is a serialised short channel id (SCID).
21+
type SerialisedScid uint64
22+
23+
// MaxMessageType is the maximum supported message type value.
24+
const MaxMessageType = lnwire.MessageType(math.MaxUint16)
25+
26+
// TapMessageTypeBaseOffset is the taproot-assets specific message type
27+
// identifier base offset. All tap messages will have a type identifier that is
28+
// greater than this value.
29+
//
30+
// This offset was chosen as the concatenation of the alphabetical index
31+
// positions of the letters "t" (20), "a" (1), and "p" (16).
32+
const TapMessageTypeBaseOffset = 20116 + lnwire.CustomTypeStart
33+
34+
const (
35+
// MsgTypeRequest is the message type identifier for a quote request
36+
// message.
37+
MsgTypeRequest = TapMessageTypeBaseOffset + 0
38+
39+
// MsgTypeAccept is the message type identifier for a quote accept
40+
// message.
41+
MsgTypeAccept = TapMessageTypeBaseOffset + 1
42+
43+
// MsgTypeReject is the message type identifier for a quote
44+
// reject message.
45+
MsgTypeReject = TapMessageTypeBaseOffset + 2
46+
)
47+
48+
var (
49+
// ErrUnknownMessageType is an error that is returned when an unknown
50+
// message type is encountered.
51+
ErrUnknownMessageType = errors.New("unknown message type")
52+
)
53+
54+
// WireMessage is a struct that represents a general wire message.
55+
type WireMessage struct {
56+
// Peer is the origin/destination peer for this message.
57+
Peer route.Vertex
58+
59+
// MsgType is the protocol message type number.
60+
MsgType lnwire.MessageType
61+
62+
// Data is the data exchanged.
63+
Data []byte
64+
}
65+
66+
// NewIncomingMsgFromWire creates a new RFQ message from a wire message.
67+
func NewIncomingMsgFromWire(wireMsg WireMessage) (IncomingMsg, error) {
68+
switch wireMsg.MsgType {
69+
case MsgTypeRequest:
70+
return NewRequestMsgFromWire(wireMsg)
71+
case MsgTypeAccept:
72+
return NewAcceptFromWireMsg(wireMsg)
73+
case MsgTypeReject:
74+
return NewQuoteRejectFromWireMsg(wireMsg)
75+
default:
76+
return nil, ErrUnknownMessageType
77+
}
78+
}
79+
80+
// IncomingMsg is an interface that represents an inbound wire message
81+
// that has been received from a peer.
82+
type IncomingMsg interface {
83+
// String returns a human-readable string representation of the message.
84+
String() string
85+
}
86+
87+
// OutgoingMsg is an interface that represents an outbound wire message
88+
// that can be sent to a peer.
89+
type OutgoingMsg interface {
90+
// String returns a human-readable string representation of the message.
91+
String() string
92+
93+
// ToWire returns a wire message with a serialized data field.
94+
ToWire() (WireMessage, error)
95+
}

0 commit comments

Comments
 (0)