Skip to content

Add version fields to RFQ wire messages #911

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

Merged
merged 6 commits into from
May 22, 2024
Merged
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
38 changes: 33 additions & 5 deletions rfqmsg/buy_accept.go
Original file line number Diff line number Diff line change
@@ -15,13 +15,23 @@ import (
const (
// Buy accept message type field TLV types.

TypeBuyAcceptID tlv.Type = 0
TypeBuyAcceptAskPrice tlv.Type = 2
TypeBuyAcceptExpiry tlv.Type = 4
TypeBuyAcceptSignature tlv.Type = 6
TypeBuyAcceptAssetID tlv.Type = 8
TypeBuyAcceptVersion tlv.Type = 0
TypeBuyAcceptID tlv.Type = 2
TypeBuyAcceptAskPrice tlv.Type = 4
TypeBuyAcceptExpiry tlv.Type = 6
TypeBuyAcceptSignature tlv.Type = 8
TypeBuyAcceptAssetID tlv.Type = 10
)

func TypeRecordBuyAcceptVersion(version *WireMsgDataVersion) tlv.Record {
const recordSize = 1

return tlv.MakeStaticRecord(
TypeBuyAcceptVersion, version, recordSize,
WireMsgDataVersionEncoder, WireMsgDataVersionDecoder,
)
}

func TypeRecordBuyAcceptID(id *ID) tlv.Record {
const recordSize = 32

@@ -80,9 +90,18 @@ func TypeRecordBuyAcceptAssetID(assetID **asset.ID) tlv.Record {
)
}

const (
// latestBuyAcceptVersion is the latest supported buy accept wire
// message data field version.
latestBuyAcceptVersion = V0
)

// buyAcceptMsgData is a struct that represents the data field of a quote
// accept message.
type buyAcceptMsgData struct {
// Version is the version of the message data.
Version WireMsgDataVersion

// ID represents the unique identifier of the quote request message that
// this response is associated with.
ID ID
@@ -104,6 +123,7 @@ type buyAcceptMsgData struct {
// encodeRecords provides all TLV records for encoding.
func (q *buyAcceptMsgData) encodeRecords() []tlv.Record {
records := []tlv.Record{
TypeRecordBuyAcceptVersion(&q.Version),
TypeRecordBuyAcceptID(&q.ID),
TypeRecordBuyAcceptAskPrice(&q.AskPrice),
TypeRecordBuyAcceptExpiry(&q.Expiry),
@@ -122,6 +142,7 @@ func (q *buyAcceptMsgData) encodeRecords() []tlv.Record {
// decodeRecords provides all TLV records for decoding.
func (q *buyAcceptMsgData) decodeRecords() []tlv.Record {
return []tlv.Record{
TypeRecordBuyAcceptVersion(&q.Version),
TypeRecordBuyAcceptID(&q.ID),
TypeRecordBuyAcceptAskPrice(&q.AskPrice),
TypeRecordBuyAcceptExpiry(&q.Expiry),
@@ -181,6 +202,7 @@ func NewBuyAcceptFromRequest(request BuyRequest, askPrice lnwire.MilliSatoshi,
Peer: request.Peer,
AssetAmount: request.AssetAmount,
buyAcceptMsgData: buyAcceptMsgData{
Version: latestBuyAcceptVersion,
ID: request.ID,
AskPrice: askPrice,
Expiry: expiry,
@@ -205,6 +227,12 @@ func NewBuyAcceptFromWireMsg(wireMsg WireMessage) (*BuyAccept, error) {
"message data: %w", err)
}

// Ensure that the message version is supported.
if msgData.Version > latestBuyAcceptVersion {
return nil, fmt.Errorf("unsupported buy accept message "+
"version: %d", msgData.Version)
}

return &BuyAccept{
Peer: wireMsg.Peer,
buyAcceptMsgData: msgData,
3 changes: 3 additions & 0 deletions rfqmsg/buy_accept_test.go
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ func TestBuyAcceptMsgDataEncodeDecode(t *testing.T) {
testCases := []struct {
testName string

version WireMsgDataVersion
id ID
askPrice lnwire.MilliSatoshi
expiry uint64
@@ -68,6 +69,7 @@ func TestBuyAcceptMsgDataEncodeDecode(t *testing.T) {
}{
{
testName: "all fields populated with basic values",
version: latestBuyAcceptVersion,
id: id,
askPrice: 1000,
expiry: 42000,
@@ -82,6 +84,7 @@ func TestBuyAcceptMsgDataEncodeDecode(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.testName, func(tt *testing.T) {
msg := buyAcceptMsgData{
Version: tc.version,
ID: tc.id,
AskPrice: tc.askPrice,
Expiry: tc.expiry,
44 changes: 36 additions & 8 deletions rfqmsg/buy_request.go
Original file line number Diff line number Diff line change
@@ -17,13 +17,23 @@ import (
const (
// Buy request message type field TLV types.

TypeBuyRequestID tlv.Type = 0
TypeBuyRequestAssetID tlv.Type = 1
TypeBuyRequestAssetGroupKey tlv.Type = 3
TypeBuyRequestAssetAmount tlv.Type = 4
TypeBuyRequestBidPrice tlv.Type = 6
TypeBuyRequestVersion tlv.Type = 0
TypeBuyRequestID tlv.Type = 2
TypeBuyRequestAssetID tlv.Type = 3
TypeBuyRequestAssetGroupKey tlv.Type = 5
TypeBuyRequestAssetAmount tlv.Type = 6
TypeBuyRequestBidPrice tlv.Type = 8
)

func TypeRecordBuyRequestVersion(version *WireMsgDataVersion) tlv.Record {
const recordSize = 1

return tlv.MakeStaticRecord(
TypeBuyRequestVersion, version, recordSize,
WireMsgDataVersionEncoder, WireMsgDataVersionDecoder,
)
}

func TypeRecordBuyRequestID(id *ID) tlv.Record {
const recordSize = 32

@@ -121,9 +131,18 @@ func TypeRecordBuyRequestBidPrice(bid *lnwire.MilliSatoshi) tlv.Record {
)
}

const (
// latestBuyRequestVersion is the latest supported buy request wire
// message data field version.
latestBuyRequestVersion = V0
)

// buyRequestMsgData is a struct that represents the message data from an asset
// buy quote request message.
type buyRequestMsgData struct {
// Version is the version of the message data.
Version WireMsgDataVersion

// ID is the unique identifier of the quote request.
ID ID

@@ -154,15 +173,22 @@ func (q *buyRequestMsgData) Validate() error {
"non-nil")
}

// Ensure that the message version is supported.
if q.Version > latestBuyRequestVersion {
return fmt.Errorf("unsupported buy request message version: %d",
q.Version)
}

return nil
}

// EncodeRecords determines the non-nil records to include when encoding an at
// runtime.
func (q *buyRequestMsgData) encodeRecords() []tlv.Record {
var records []tlv.Record

records = append(records, TypeRecordBuyRequestID(&q.ID))
records := []tlv.Record{
TypeRecordBuyRequestVersion(&q.Version),
TypeRecordBuyRequestID(&q.ID),
}

if q.AssetID != nil {
records = append(
@@ -206,6 +232,7 @@ func (q *buyRequestMsgData) Bytes() ([]byte, error) {
// DecodeRecords provides all TLV records for decoding.
func (q *buyRequestMsgData) decodeRecords() []tlv.Record {
return []tlv.Record{
TypeRecordBuyRequestVersion(&q.Version),
TypeRecordBuyRequestID(&q.ID),
TypeRecordBuyRequestAssetID(&q.AssetID),
TypeRecordBuyRequestAssetGroupKey(&q.AssetGroupKey),
@@ -248,6 +275,7 @@ func NewBuyRequest(peer route.Vertex, assetID *asset.ID,
return &BuyRequest{
Peer: peer,
buyRequestMsgData: buyRequestMsgData{
Version: latestBuyRequestVersion,
ID: id,
AssetID: assetID,
AssetGroupKey: assetGroupKey,
12 changes: 12 additions & 0 deletions rfqmsg/buy_request_test.go
Original file line number Diff line number Diff line change
@@ -27,14 +27,25 @@ func TestBuyRequestMsgDataEncodeDecode(t *testing.T) {
testCases := []struct {
testName string

version WireMsgDataVersion
id ID
assetId *asset.ID
assetGroupKey *btcec.PublicKey
assetAmount uint64
bidPrice lnwire.MilliSatoshi
}{
{
testName: "asset group key nil, version 0",
version: 0,
id: id,
assetId: &assetId,
assetGroupKey: nil,
assetAmount: 1000,
bidPrice: lnwire.MilliSatoshi(42000),
},
{
testName: "asset group key nil",
version: 5,
id: id,
assetId: &assetId,
assetGroupKey: nil,
@@ -46,6 +57,7 @@ func TestBuyRequestMsgDataEncodeDecode(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.testName, func(tt *testing.T) {
req := buyRequestMsgData{
Version: tc.version,
ID: tc.id,
AssetID: tc.assetId,
AssetGroupKey: tc.assetGroupKey,
40 changes: 40 additions & 0 deletions rfqmsg/messages.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"encoding/binary"
"encoding/hex"
"errors"
"io"
"math"

"github.com/lightningnetwork/lnd/lnwire"
@@ -115,6 +116,45 @@ func NewIncomingMsgFromWire(wireMsg WireMessage) (IncomingMsg, error) {
}
}

// WireMsgDataVersion specifies the version of the contents within a wire
// message data field.
type WireMsgDataVersion uint8

const (
// V0 represents version 0 of the contents in a wire message data field.
V0 WireMsgDataVersion = 0
)

// WireMsgDataVersionEncoder is a function that can be used to encode a
// WireMsgDataVersion to a writer.
func WireMsgDataVersionEncoder(w io.Writer, val any, buf *[8]byte) error {
if version, ok := val.(*WireMsgDataVersion); ok {
versionUint8 := uint8(*version)
return tlv.EUint8(w, &versionUint8, buf)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why not EUint8T like in asset.VersionEncoder?

return tlv.EUint8T(w, uint8(*version), buf)

Copy link
Contributor Author

@ffranr ffranr May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should have been tlv.EUint8T. I'll try to keep that in mind in the future. This PR was merged before I got a chance to modify it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sg, could be in the follow-up PR you mentioned above.

}

return tlv.NewTypeForEncodingErr(val, "WireMsgDataVersion")
}

// WireMsgDataVersionDecoder is a function that can be used to decode a
// WireMsgDataVersion from a reader.
func WireMsgDataVersionDecoder(r io.Reader, val any, buf *[8]byte,
l uint64) error {

if version, ok := val.(*WireMsgDataVersion); ok {
var versionInt uint8
err := tlv.DUint8(r, &versionInt, buf, l)
if err != nil {
return err
}

*version = WireMsgDataVersion(versionInt)
return nil
}

return tlv.NewTypeForDecodingErr(val, "WireMsgDataVersion", l, 8)
}

// IncomingMsg is an interface that represents an inbound wire message
// that has been received from a peer.
type IncomingMsg interface {
38 changes: 33 additions & 5 deletions rfqmsg/reject.go
Original file line number Diff line number Diff line change
@@ -12,11 +12,21 @@ import (
const (
// Reject message type field TLV types.

TypeRejectID tlv.Type = 0
TypeRejectErrCode tlv.Type = 1
TypeRejectErrMsg tlv.Type = 3
TypeRejectVersion tlv.Type = 0
TypeRejectID tlv.Type = 2
TypeRejectErrCode tlv.Type = 3
TypeRejectErrMsg tlv.Type = 5
)

func TypeRecordRejectVersion(version *WireMsgDataVersion) tlv.Record {
const recordSize = 1

return tlv.MakeStaticRecord(
TypeRejectVersion, version, recordSize,
WireMsgDataVersionEncoder, WireMsgDataVersionDecoder,
)
}

func TypeRecordRejectID(id *ID) tlv.Record {
const recordSize = 32

@@ -102,9 +112,18 @@ var (
}
)

const (
// latestRejectVersion is the latest supported reject wire message data
// field version.
latestRejectVersion = V0
)

// rejectMsgData is a struct that represents the data field of a quote
// reject message.
type rejectMsgData struct {
// Version is the version of the message data.
Version WireMsgDataVersion

// ID represents the unique identifier of the quote request message that
// this response is associated with.
ID ID
@@ -118,6 +137,7 @@ type rejectMsgData struct {
// runtime.
func (q *rejectMsgData) encodeRecords() []tlv.Record {
return []tlv.Record{
TypeRecordRejectVersion(&q.Version),
TypeRecordRejectID(&q.ID),
TypeRecordRejectErrCode(&q.Err.Code),
TypeRecordRejectErrMsg(&q.Err.Msg),
@@ -136,6 +156,7 @@ func (q *rejectMsgData) Encode(writer io.Writer) error {
// DecodeRecords provides all TLV records for decoding.
func (q *rejectMsgData) decodeRecords() []tlv.Record {
return []tlv.Record{
TypeRecordRejectVersion(&q.Version),
TypeRecordRejectID(&q.ID),
TypeRecordRejectErrCode(&q.Err.Code),
TypeRecordRejectErrMsg(&q.Err.Msg),
@@ -178,8 +199,9 @@ func NewReject(peer route.Vertex, requestId ID,
return &Reject{
Peer: peer,
rejectMsgData: rejectMsgData{
ID: requestId,
Err: rejectErr,
Version: latestRejectVersion,
ID: requestId,
Err: rejectErr,
},
}
}
@@ -200,6 +222,12 @@ func NewQuoteRejectFromWireMsg(wireMsg WireMessage) (*Reject, error) {
"message data: %w", err)
}

// Ensure that the message version is supported.
if msgData.Version > latestRejectVersion {
return nil, fmt.Errorf("unsupported reject message version: %d",
msgData.Version)
}

return &Reject{
Peer: wireMsg.Peer,
rejectMsgData: msgData,
16 changes: 13 additions & 3 deletions rfqmsg/reject_test.go
Original file line number Diff line number Diff line change
@@ -20,13 +20,23 @@ func TestRejectEncodeDecode(t *testing.T) {
testCases := []struct {
testName string

peer route.Vertex
id ID
err RejectErr
peer route.Vertex
version WireMsgDataVersion
id ID
err RejectErr
}{
{
testName: "all fields populated with basic values, " +
"zero version",
peer: route.Vertex{1, 2, 3},
version: 0,
id: id,
err: ErrNoSuitableBuyOffer,
},
{
testName: "all fields populated with basic values",
peer: route.Vertex{1, 2, 3},
version: 5,
id: id,
err: ErrNoSuitableBuyOffer,
},
Loading