From d319b69b2bf252e92ee19a084fed0d35b5496862 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 7 Jun 2024 15:35:44 +0100 Subject: [PATCH 1/6] rfqmsg: add quote accept message type This commit adds a quote accept message type which serves as a single wire message type for both buy and sell quote accept messages. --- rfqmsg/accept.go | 260 ++++++++++++++++++++++++++++++++++++++++++ rfqmsg/buy_accept.go | 54 +++++---- rfqmsg/messages.go | 16 +-- rfqmsg/sell_accept.go | 58 ++++++---- 4 files changed, 336 insertions(+), 52 deletions(-) create mode 100644 rfqmsg/accept.go diff --git a/rfqmsg/accept.go b/rfqmsg/accept.go new file mode 100644 index 000000000..d81e17fb6 --- /dev/null +++ b/rfqmsg/accept.go @@ -0,0 +1,260 @@ +package rfqmsg + +import ( + "bytes" + "fmt" + "io" + "time" + + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // latestAcceptWireMsgDataVersion is the latest supported quote accept + // wire message data field version. + latestAcceptWireMsgDataVersion = V0 +) + +type ( + // acceptInOutRateTick is a type alias for a record that represents the + // in-out rate tick of a quote accept message. + acceptInOutRateTick = tlv.OptionalRecordT[tlv.TlvType4, uint64] + + // acceptOutInRateTick is a type alias for a record that represents the + // out-in rate tick of a quote accept message. + acceptOutInRateTick = tlv.OptionalRecordT[tlv.TlvType5, uint64] +) + +// acceptWireMsgData is a struct that represents the message data field for +// a quote accept wire message. +type acceptWireMsgData struct { + // Version is the version of the message data. + Version tlv.RecordT[tlv.TlvType0, WireMsgDataVersion] + + // ID is the unique identifier of the quote request. + ID tlv.RecordT[tlv.TlvType1, ID] + + // Expiry is the expiry Unix timestamp (in seconds) of the quote + // request. This timestamp defines the lifetime of both the suggested + // rate tick and the quote request. + Expiry tlv.RecordT[tlv.TlvType2, uint64] + + // Sig is a signature over the serialized contents of the message. + Sig tlv.RecordT[tlv.TlvType3, [64]byte] + + InOutRateTick acceptInOutRateTick + + OutInRateTick acceptOutInRateTick +} + +// newAcceptWireMsgDataFromBuy creates a new acceptWireMsgData from a buy +// accept message. +func newAcceptWireMsgDataFromBuy(q BuyAccept) acceptWireMsgData { + version := tlv.NewPrimitiveRecord[tlv.TlvType0](q.Version) + id := tlv.NewRecordT[tlv.TlvType1](q.ID) + expiry := tlv.NewPrimitiveRecord[tlv.TlvType2](q.Expiry) + sig := tlv.NewPrimitiveRecord[tlv.TlvType3](q.sig) + + // When processing a buy request/accept, the incoming asset must be + // specified. Currently, we assume the outgoing asset is BTC, + // considering the perspective of the quote request initiator. + // To indicate that this quote accept wire message is for a buy request, + // we set the in-out rate tick instead of the out-in rate tick. + inOutRateTick := tlv.SomeRecordT[tlv.TlvType4]( + tlv.NewPrimitiveRecord[tlv.TlvType4]( + uint64(q.AskPrice), + ), + ) + + // Encode message data component as TLV bytes. + return acceptWireMsgData{ + Version: version, + ID: id, + Expiry: expiry, + Sig: sig, + InOutRateTick: inOutRateTick, + } +} + +// newAcceptWireMsgDataFromSell creates a new acceptWireMsgData from a sell +// accept message. +func newAcceptWireMsgDataFromSell(q SellAccept) acceptWireMsgData { + version := tlv.NewPrimitiveRecord[tlv.TlvType0](q.Version) + id := tlv.NewRecordT[tlv.TlvType1](q.ID) + expiry := tlv.NewPrimitiveRecord[tlv.TlvType2](q.Expiry) + sig := tlv.NewPrimitiveRecord[tlv.TlvType3](q.sig) + + // When processing a sell request/accept, the outgoing asset must be + // specified. Currently, we assume the incoming asset is BTC, + // considering the perspective of the quote request initiator. + // To indicate that this quote accept wire message is for a sell + // request, we set the out-in rate tick instead of the in-out rate tick. + outInRateTick := tlv.SomeRecordT[tlv.TlvType5]( + tlv.NewPrimitiveRecord[tlv.TlvType5]( + uint64(q.BidPrice), + ), + ) + + // Encode message data component as TLV bytes. + return acceptWireMsgData{ + Version: version, + ID: id, + Expiry: expiry, + Sig: sig, + OutInRateTick: outInRateTick, + } +} + +// Validate ensures that the quote accept message is valid. +func (m *acceptWireMsgData) Validate() error { + // Ensure the version specified in the version field is supported. + if m.Version.Val > latestAcceptWireMsgDataVersion { + return fmt.Errorf("unsupported quote accept message data "+ + "version: %d", m.Version.Val) + } + + // Ensure that the expiry is set to a future time. + if m.Expiry.Val <= uint64(time.Now().Unix()) { + return fmt.Errorf("expiry must be set to a future time") + } + + // Ensure that at least one of the rate ticks is set. + if m.InOutRateTick.IsNone() && m.OutInRateTick.IsNone() { + return fmt.Errorf("at least one of the rate ticks must be set") + } + + // Ensure that both rate ticks are not set. + if m.InOutRateTick.IsSome() && m.OutInRateTick.IsSome() { + return fmt.Errorf("both rate ticks cannot be set") + } + + return nil +} + +// Encode serializes the acceptWireMsgData to the given io.Writer. +func (m *acceptWireMsgData) Encode(w io.Writer) error { + // Validate the message before encoding. + err := m.Validate() + if err != nil { + return err + } + + records := []tlv.Record{ + m.Version.Record(), + m.ID.Record(), + m.Expiry.Record(), + m.Sig.Record(), + } + + m.InOutRateTick.WhenSome( + func(r tlv.RecordT[tlv.TlvType4, uint64]) { + records = append(records, r.Record()) + }, + ) + + m.OutInRateTick.WhenSome( + func(r tlv.RecordT[tlv.TlvType5, uint64]) { + records = append(records, r.Record()) + }, + ) + + tlv.SortRecords(records) + + // Create the tlv stream. + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// Decode deserializes the acceptWireMsgData from the given io.Reader. +func (m *acceptWireMsgData) Decode(r io.Reader) error { + // Define zero values for optional fields. + inOutRateTick := m.InOutRateTick.Zero() + outInRateTick := m.OutInRateTick.Zero() + + // Create a tlv stream with all the fields. + tlvStream, err := tlv.NewStream( + m.Version.Record(), + m.ID.Record(), + m.Expiry.Record(), + m.Sig.Record(), + + inOutRateTick.Record(), + outInRateTick.Record(), + ) + if err != nil { + return err + } + + // Decode the reader's contents into the tlv stream. + tlvMap, err := tlvStream.DecodeWithParsedTypes(r) + if err != nil { + return err + } + + // Set optional fields if they are present. + if _, ok := tlvMap[inOutRateTick.TlvType()]; ok { + m.InOutRateTick = tlv.SomeRecordT(inOutRateTick) + } + + if _, ok := tlvMap[outInRateTick.TlvType()]; ok { + m.OutInRateTick = tlv.SomeRecordT(outInRateTick) + } + + return nil +} + +// Bytes encodes the structure into a TLV stream and returns the bytes. +func (m *acceptWireMsgData) Bytes() ([]byte, error) { + var b bytes.Buffer + err := m.Encode(&b) + if err != nil { + return nil, err + } + + return b.Bytes(), nil +} + +// NewIncomingAcceptFromWire creates a new quote accept message from an incoming +// wire message. +// +// This is an incoming accept message. An incoming buy accept message indicates +// that our peer accepts our buy request, meaning they are willing to sell the +// asset to us. Conversely, an incoming sell accept message indicates that our +// peer accepts our sell request, meaning they are willing to buy the asset from +// us. +func NewIncomingAcceptFromWire(wireMsg WireMessage) (IncomingMsg, error) { + // Ensure that the message type is a quote accept message. + if wireMsg.MsgType != MsgTypeAccept { + return nil, ErrUnknownMessageType + } + + var msgData acceptWireMsgData + err := msgData.Decode(bytes.NewBuffer(wireMsg.Data)) + if err != nil { + return nil, fmt.Errorf("unable to decode incoming quote "+ + "accept message data: %w", err) + } + + if err := msgData.Validate(); err != nil { + return nil, fmt.Errorf("unable to validate incoming "+ + "quote accept message: %w", err) + } + + // We will now determine whether this is a buy or sell accept. We can + // distinguish between buy/sell accept messages by inspecting which tick + // rate field is populated. + isBuyAccept := msgData.InOutRateTick.IsSome() + + // If this is a buy request, then we will create a new buy request + // message. + if isBuyAccept { + return newBuyAcceptFromWireMsg(wireMsg, msgData) + } + + // Otherwise, this is a sell request. + return newSellAcceptFromWireMsg(wireMsg, msgData) +} diff --git a/rfqmsg/buy_accept.go b/rfqmsg/buy_accept.go index c4122676b..200ccb7c7 100644 --- a/rfqmsg/buy_accept.go +++ b/rfqmsg/buy_accept.go @@ -167,6 +167,9 @@ type BuyAccept struct { // AssetAmount is the amount of the asset that the accept message // is for. + // + // TODO(ffranr): Remove this field in favour of the asset amount from + // the request. AssetAmount uint64 // buyAcceptMsgData is the message data for the quote accept message. @@ -191,31 +194,36 @@ func NewBuyAcceptFromRequest(request BuyRequest, askPrice lnwire.MilliSatoshi, } } -// NewBuyAcceptFromWireMsg instantiates a new instance from a wire message. -func NewBuyAcceptFromWireMsg(wireMsg WireMessage) (*BuyAccept, error) { +// newBuyAcceptFromWireMsg instantiates a new instance from a wire message. +func newBuyAcceptFromWireMsg(wireMsg WireMessage, + msgData acceptWireMsgData) (*BuyAccept, error) { + // Ensure that the message type is an accept message. - if wireMsg.MsgType != MsgTypeBuyAccept { + if wireMsg.MsgType != MsgTypeAccept { return nil, fmt.Errorf("unable to create an accept message "+ "from wire message of type %d", wireMsg.MsgType) } - // Decode message data component from TLV bytes. - var msgData buyAcceptMsgData - err := msgData.Decode(bytes.NewReader(wireMsg.Data)) - if err != nil { - return nil, fmt.Errorf("unable to decode quote accept "+ - "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) - } + // Extract the rate tick from the in-out rate tick field. We use this + // field (and not the out-in rate tick field) because this is the rate + // tick field populated in response to a peer initiated buy quote + // request. + var askPrice lnwire.MilliSatoshi + msgData.InOutRateTick.WhenSome( + func(rate tlv.RecordT[tlv.TlvType4, uint64]) { + askPrice = lnwire.MilliSatoshi(rate.Val) + }, + ) return &BuyAccept{ - Peer: wireMsg.Peer, - buyAcceptMsgData: msgData, + Peer: wireMsg.Peer, + buyAcceptMsgData: buyAcceptMsgData{ + Version: msgData.Version.Val, + ID: msgData.ID.Val, + Expiry: msgData.Expiry.Val, + sig: msgData.Sig.Val, + AskPrice: askPrice, + }, }, nil } @@ -229,8 +237,14 @@ func (q *BuyAccept) ShortChannelId() SerialisedScid { // TODO(ffranr): This method should accept a signer so that we can generate a // signature over the message data. func (q *BuyAccept) ToWire() (WireMessage, error) { + if q == nil { + return WireMessage{}, fmt.Errorf("cannot serialize nil buy " + + "accept") + } + // Encode message data component as TLV bytes. - msgDataBytes, err := q.buyAcceptMsgData.Bytes() + msgData := newAcceptWireMsgDataFromBuy(*q) + msgDataBytes, err := msgData.Bytes() if err != nil { return WireMessage{}, fmt.Errorf("unable to encode message "+ "data: %w", err) @@ -238,7 +252,7 @@ func (q *BuyAccept) ToWire() (WireMessage, error) { return WireMessage{ Peer: q.Peer, - MsgType: MsgTypeBuyAccept, + MsgType: MsgTypeAccept, Data: msgDataBytes, }, nil } diff --git a/rfqmsg/messages.go b/rfqmsg/messages.go index 6da040179..0a3371ba3 100644 --- a/rfqmsg/messages.go +++ b/rfqmsg/messages.go @@ -63,17 +63,13 @@ const ( // message. MsgTypeRequest = TapMessageTypeBaseOffset + 0 - // MsgTypeBuyAccept is the message type identifier for a quote accept + // MsgTypeAccept is the message type identifier for a quote accept // message. - MsgTypeBuyAccept = TapMessageTypeBaseOffset + 1 - - // MsgTypeSellAccept is the message type identifier for an asset sell - // quote accept message. - MsgTypeSellAccept = TapMessageTypeBaseOffset + 3 + MsgTypeAccept = TapMessageTypeBaseOffset + 1 // MsgTypeReject is the message type identifier for a quote // reject message. - MsgTypeReject = TapMessageTypeBaseOffset + 4 + MsgTypeReject = TapMessageTypeBaseOffset + 2 ) var ( @@ -99,10 +95,8 @@ func NewIncomingMsgFromWire(wireMsg WireMessage) (IncomingMsg, error) { switch wireMsg.MsgType { case MsgTypeRequest: return NewIncomingRequestFromWire(wireMsg) - case MsgTypeBuyAccept: - return NewBuyAcceptFromWireMsg(wireMsg) - case MsgTypeSellAccept: - return NewSellAcceptFromWireMsg(wireMsg) + case MsgTypeAccept: + return NewIncomingAcceptFromWire(wireMsg) case MsgTypeReject: return NewQuoteRejectFromWireMsg(wireMsg) default: diff --git a/rfqmsg/sell_accept.go b/rfqmsg/sell_accept.go index b1d1d382f..ccb63cc51 100644 --- a/rfqmsg/sell_accept.go +++ b/rfqmsg/sell_accept.go @@ -142,6 +142,9 @@ type SellAccept struct { // AssetAmount is the amount of the asset that the accept message // is for. + // + // TODO(ffranr): Remove this field in favour of the asset amount from + // the request. AssetAmount uint64 // sellAcceptMsgData is the message data for the quote accept message. @@ -166,32 +169,39 @@ func NewSellAcceptFromRequest(request SellRequest, bidPrice lnwire.MilliSatoshi, } } -// NewSellAcceptFromWireMsg instantiates a new instance from a wire message. -func NewSellAcceptFromWireMsg(wireMsg WireMessage) (*SellAccept, error) { +// newSellAcceptFromWireMsg instantiates a new instance from a wire message. +func newSellAcceptFromWireMsg(wireMsg WireMessage, + msgData acceptWireMsgData) (*SellAccept, error) { + // Ensure that the message type is an accept message. - if wireMsg.MsgType != MsgTypeSellAccept { + if wireMsg.MsgType != MsgTypeAccept { return nil, fmt.Errorf("unable to create an asset sell "+ "accept message from wire message of type %d", wireMsg.MsgType) } - // Decode message data component from TLV bytes. - var msgData sellAcceptMsgData - err := msgData.Decode(bytes.NewReader(wireMsg.Data)) - if err != nil { - return nil, fmt.Errorf("unable to decode sell accept "+ - "message data: %w", err) - } - - // Ensure that the message version is supported. - if msgData.Version > latestSellAcceptVersion { - return nil, fmt.Errorf("unsupported sell accept message "+ - "version: %d", msgData.Version) - } + // Extract the rate tick from the out-in rate tick field. We use this + // field (and not the in-out rate tick field) because this is the rate + // tick field populated in response to a peer initiated sell quote + // request. + var bidPrice lnwire.MilliSatoshi + msgData.OutInRateTick.WhenSome( + func(rate tlv.RecordT[tlv.TlvType5, uint64]) { + bidPrice = lnwire.MilliSatoshi(rate.Val) + }, + ) + // Note that the `Request` field is populated later in the RFQ stream + // service. return &SellAccept{ - Peer: wireMsg.Peer, - sellAcceptMsgData: msgData, + Peer: wireMsg.Peer, + sellAcceptMsgData: sellAcceptMsgData{ + Version: msgData.Version.Val, + ID: msgData.ID.Val, + Expiry: msgData.Expiry.Val, + sig: msgData.Sig.Val, + BidPrice: bidPrice, + }, }, nil } @@ -212,8 +222,14 @@ func (q *SellAccept) ShortChannelId() SerialisedScid { // TODO(ffranr): This method should accept a signer so that we can generate a // signature over the message data. func (q *SellAccept) ToWire() (WireMessage, error) { - // Encode message data component as TLV bytes. - msgDataBytes, err := q.sellAcceptMsgData.Bytes() + if q == nil { + return WireMessage{}, fmt.Errorf("cannot serialize nil sell " + + "accept") + } + + // Formulate the message data. + msgData := newAcceptWireMsgDataFromSell(*q) + msgDataBytes, err := msgData.Bytes() if err != nil { return WireMessage{}, fmt.Errorf("unable to encode message "+ "data: %w", err) @@ -221,7 +237,7 @@ func (q *SellAccept) ToWire() (WireMessage, error) { return WireMessage{ Peer: q.Peer, - MsgType: MsgTypeSellAccept, + MsgType: MsgTypeAccept, Data: msgDataBytes, }, nil } From a75c74707436441061997ea0bbd7875e6e7a39a1 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 7 Jun 2024 16:01:21 +0100 Subject: [PATCH 2/6] rfqmsg: add accept message type encode/decode unit test --- rfqmsg/accept_test.go | 134 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 rfqmsg/accept_test.go diff --git a/rfqmsg/accept_test.go b/rfqmsg/accept_test.go new file mode 100644 index 000000000..70503e63f --- /dev/null +++ b/rfqmsg/accept_test.go @@ -0,0 +1,134 @@ +package rfqmsg + +import ( + "bytes" + "testing" + "time" + + "github.com/lightninglabs/taproot-assets/internal/test" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// acceptEncodeDecodeTC is a test case for encoding and decoding a +// acceptWireMsgData. +type acceptEncodeDecodeTC struct { + testName string + + version WireMsgDataVersion + id ID + expiry uint64 + sig [64]byte + + inOutRateTick *uint64 + outInRateTick *uint64 +} + +// MsgData generates a acceptWireMsgData instance from the test case. +func (tc acceptEncodeDecodeTC) MsgData() acceptWireMsgData { + version := tlv.NewPrimitiveRecord[tlv.TlvType0](tc.version) + id := tlv.NewPrimitiveRecord[tlv.TlvType1](tc.id) + expiry := tlv.NewPrimitiveRecord[tlv.TlvType2](tc.expiry) + sig := tlv.NewPrimitiveRecord[tlv.TlvType3](tc.sig) + + var inOutRateTick acceptInOutRateTick + if tc.inOutRateTick != nil { + inOutRateTick = tlv.SomeRecordT[tlv.TlvType4]( + tlv.NewPrimitiveRecord[tlv.TlvType4]( + *tc.inOutRateTick, + ), + ) + } + + var outInRateTick acceptOutInRateTick + if tc.outInRateTick != nil { + outInRateTick = tlv.SomeRecordT[tlv.TlvType5]( + tlv.NewPrimitiveRecord[tlv.TlvType5]( + *tc.outInRateTick, + ), + ) + } + + return acceptWireMsgData{ + Version: version, + ID: id, + Expiry: expiry, + Sig: sig, + InOutRateTick: inOutRateTick, + OutInRateTick: outInRateTick, + } +} + +// TestAcceptMsgDataEncodeDecode tests acceptWireMsgData encoding/decoding. +func TestAcceptMsgDataEncodeDecode(t *testing.T) { + t.Parallel() + + // Create a random ID. + randomIdBytes := test.RandBytes(32) + id := ID(randomIdBytes) + + // Compute a future expiry timestamp. + expiry := uint64(time.Now().Add(time.Hour).Unix()) + + // Create a signature. + randomSigBytes := test.RandBytes(64) + var randSig [64]byte + copy(randSig[:], randomSigBytes) + + // Crate an all zero signature. + var zeroSig [64]byte + + inOutRateTick := uint64(42000) + outInRateTick := uint64(22000) + + testCases := []acceptEncodeDecodeTC{ + { + testName: "rand sig, in-out rate tick set, out-in " + + "rate tick unset", + version: 0, + id: id, + expiry: expiry, + sig: randSig, + inOutRateTick: &inOutRateTick, + }, + { + testName: "rand sig, in-out rate tick unset, out-in " + + "rate tick set", + version: 0, + id: id, + expiry: expiry, + sig: randSig, + outInRateTick: &outInRateTick, + }, + { + testName: "zero sig, in-out rate tick unset, out-in " + + "rate tick set", + version: 0, + id: id, + expiry: expiry, + sig: zeroSig, + outInRateTick: &outInRateTick, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(tt *testing.T) { + msgData := tc.MsgData() + + // Encode the message. + msgDataBytes, err := msgData.Bytes() + require.NoError(tt, err, "unable to encode message") + + // Decode the message. + decodedMsgData := acceptWireMsgData{} + err = decodedMsgData.Decode( + bytes.NewReader(msgDataBytes), + ) + require.NoError(tt, err, "unable to decode message") + + // Assert that the decoded message is equal to + // the original message. + require.Equal(tt, msgData, decodedMsgData) + }) + } +} From af33bf4cdf2b1b20e79f233df5f1cc53ab2d9be4 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 7 Jun 2024 16:12:16 +0100 Subject: [PATCH 3/6] rfqmsg: remove redundant type sellAcceptMsgData Type `sellAcceptMsgData` is redundant and can be removed following the introduction of type `acceptWireMsgData`. --- rfqmsg/sell_accept.go | 158 ++++++------------------------------- rfqmsg/sell_accept_test.go | 75 ------------------ 2 files changed, 26 insertions(+), 207 deletions(-) delete mode 100644 rfqmsg/sell_accept_test.go diff --git a/rfqmsg/sell_accept.go b/rfqmsg/sell_accept.go index ccb63cc51..0395575dd 100644 --- a/rfqmsg/sell_accept.go +++ b/rfqmsg/sell_accept.go @@ -1,136 +1,20 @@ package rfqmsg import ( - "bytes" "encoding/binary" "fmt" - "io" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) -const ( - // Sell accept message type field TLV types. - - TypeSellAcceptVersion tlv.Type = 0 - TypeSellAcceptID tlv.Type = 2 - TypeSellAcceptBidPrice tlv.Type = 4 - TypeSellAcceptExpiry tlv.Type = 6 - TypeSellAcceptSignature tlv.Type = 8 -) - -func TypeRecordSellAcceptVersion(version *WireMsgDataVersion) tlv.Record { - const recordSize = 1 - - return tlv.MakeStaticRecord( - TypeSellAcceptVersion, version, recordSize, - WireMsgDataVersionEncoder, WireMsgDataVersionDecoder, - ) -} - -func TypeRecordSellAcceptID(id *ID) tlv.Record { - const recordSize = 32 - - return tlv.MakeStaticRecord( - TypeSellAcceptID, id, recordSize, IdEncoder, IdDecoder, - ) -} - -func TypeRecordSellAcceptBidPrice(bidPrice *lnwire.MilliSatoshi) tlv.Record { - return tlv.MakeStaticRecord( - TypeSellAcceptBidPrice, bidPrice, 8, milliSatoshiEncoder, - milliSatoshiDecoder, - ) -} - -func TypeRecordSellAcceptExpiry(expirySeconds *uint64) tlv.Record { - return tlv.MakePrimitiveRecord(TypeSellAcceptExpiry, expirySeconds) -} - -func TypeRecordSellAcceptSig(sig *[64]byte) tlv.Record { - return tlv.MakePrimitiveRecord(TypeSellAcceptSignature, sig) -} - const ( // latestSellAcceptVersion is the latest supported sell accept wire // message data field version. latestSellAcceptVersion = V0 ) -// sellAcceptMsgData is a struct that represents the data field of an asset sell -// quote request accept message. -type sellAcceptMsgData struct { - // Version is the version of the message data. - Version WireMsgDataVersion - - // ID represents the unique identifier of the asset sell quote request - // message that this response is associated with. - ID ID - - // BidPrice is the bid price that the message author is willing to pay - // for the asset that is for sale. - BidPrice lnwire.MilliSatoshi - - // Expiry is the bid price expiry lifetime unix timestamp. - Expiry uint64 - - // sig is a signature over the serialized contents of the message. - sig [64]byte -} - -// encodeRecords provides all TLV records for encoding. -func (q *sellAcceptMsgData) encodeRecords() []tlv.Record { - return []tlv.Record{ - TypeRecordSellAcceptVersion(&q.Version), - TypeRecordSellAcceptID(&q.ID), - TypeRecordSellAcceptBidPrice(&q.BidPrice), - TypeRecordSellAcceptExpiry(&q.Expiry), - TypeRecordSellAcceptSig(&q.sig), - } -} - -// decodeRecords provides all TLV records for decoding. -func (q *sellAcceptMsgData) decodeRecords() []tlv.Record { - return []tlv.Record{ - TypeRecordSellAcceptVersion(&q.Version), - TypeRecordSellAcceptID(&q.ID), - TypeRecordSellAcceptBidPrice(&q.BidPrice), - TypeRecordSellAcceptExpiry(&q.Expiry), - TypeRecordSellAcceptSig(&q.sig), - } -} - -// Encode encodes the structure into a TLV stream. -func (q *sellAcceptMsgData) Encode(writer io.Writer) error { - stream, err := tlv.NewStream(q.encodeRecords()...) - if err != nil { - return err - } - return stream.Encode(writer) -} - -// Decode decodes the structure from a TLV stream. -func (q *sellAcceptMsgData) Decode(r io.Reader) error { - stream, err := tlv.NewStream(q.decodeRecords()...) - if err != nil { - return err - } - return stream.DecodeP2P(r) -} - -// Bytes encodes the structure into a TLV stream and returns the bytes. -func (q *sellAcceptMsgData) Bytes() ([]byte, error) { - var b bytes.Buffer - err := q.Encode(&b) - if err != nil { - return nil, err - } - - return b.Bytes(), nil -} - // SellAccept is a struct that represents a sell quote request accept message. type SellAccept struct { // Peer is the peer that sent the quote request. @@ -147,8 +31,22 @@ type SellAccept struct { // the request. AssetAmount uint64 - // sellAcceptMsgData is the message data for the quote accept message. - sellAcceptMsgData + // Version is the version of the message data. + Version WireMsgDataVersion + + // ID represents the unique identifier of the asset sell quote request + // message that this response is associated with. + ID ID + + // BidPrice is the bid price that the message author is willing to pay + // for the asset that is for sale. + BidPrice lnwire.MilliSatoshi + + // Expiry is the bid price expiry lifetime unix timestamp. + Expiry uint64 + + // sig is a signature over the serialized contents of the message. + sig [64]byte } // NewSellAcceptFromRequest creates a new instance of an asset sell quote accept @@ -160,12 +58,10 @@ func NewSellAcceptFromRequest(request SellRequest, bidPrice lnwire.MilliSatoshi, Peer: request.Peer, AssetAmount: request.AssetAmount, Request: request, - sellAcceptMsgData: sellAcceptMsgData{ - Version: latestSellAcceptVersion, - ID: request.ID, - BidPrice: bidPrice, - Expiry: expiry, - }, + Version: latestSellAcceptVersion, + ID: request.ID, + BidPrice: bidPrice, + Expiry: expiry, } } @@ -194,14 +90,12 @@ func newSellAcceptFromWireMsg(wireMsg WireMessage, // Note that the `Request` field is populated later in the RFQ stream // service. return &SellAccept{ - Peer: wireMsg.Peer, - sellAcceptMsgData: sellAcceptMsgData{ - Version: msgData.Version.Val, - ID: msgData.ID.Val, - Expiry: msgData.Expiry.Val, - sig: msgData.Sig.Val, - BidPrice: bidPrice, - }, + Peer: wireMsg.Peer, + Version: msgData.Version.Val, + ID: msgData.ID.Val, + BidPrice: bidPrice, + Expiry: msgData.Expiry.Val, + sig: msgData.Sig.Val, }, nil } diff --git a/rfqmsg/sell_accept_test.go b/rfqmsg/sell_accept_test.go deleted file mode 100644 index 780e2819a..000000000 --- a/rfqmsg/sell_accept_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package rfqmsg - -import ( - "bytes" - "testing" - - "github.com/lightninglabs/taproot-assets/asset" - "github.com/lightninglabs/taproot-assets/internal/test" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// TestSellAcceptMsgDataEncodeDecode tests the encoding and decoding of a sell -// accept message. -func TestSellAcceptMsgDataEncodeDecode(t *testing.T) { - t.Parallel() - - // Create a random ID. - randomIdBytes := test.RandBytes(32) - id := ID(randomIdBytes) - - // Create a random signature. - randomSigBytes := test.RandBytes(64) - var signature [64]byte - copy(signature[:], randomSigBytes[:]) - - testCases := []struct { - testName string - - version WireMsgDataVersion - id ID - bidPrice lnwire.MilliSatoshi - expiry uint64 - sig [64]byte - assetID *asset.ID - }{ - { - testName: "all fields populated with basic values", - version: latestSellAcceptVersion, - id: id, - bidPrice: 1000, - expiry: 42000, - sig: signature, - assetID: &asset.ID{1, 2, 3}, - }, - { - testName: "empty fields", - }, - } - - for _, tc := range testCases { - t.Run(tc.testName, func(tt *testing.T) { - msg := sellAcceptMsgData{ - Version: tc.version, - ID: tc.id, - BidPrice: tc.bidPrice, - Expiry: tc.expiry, - sig: tc.sig, - } - - // Encode the message. - reqBytes, err := msg.Bytes() - require.NoError(tt, err, "unable to encode message") - - // Decode the message. - decodedMsg := sellAcceptMsgData{} - err = decodedMsg.Decode(bytes.NewReader(reqBytes)) - require.NoError(tt, err, "unable to decode message") - - // Assert that the decoded message is equal to the - // original message. - require.Equal(tt, msg, decodedMsg) - }) - } -} From 0d4010cbf6620281ae309ae7404e1c99f91d40e9 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 7 Jun 2024 16:18:18 +0100 Subject: [PATCH 4/6] rfqmsg: remove redundant type buyAcceptMsgData Type `buyAcceptMsgData` is redundant and can be removed following the introduction of type `acceptWireMsgData`. --- rfqmsg/buy_accept.go | 184 ++++++-------------------------------- rfqmsg/buy_accept_test.go | 70 +-------------- 2 files changed, 27 insertions(+), 227 deletions(-) diff --git a/rfqmsg/buy_accept.go b/rfqmsg/buy_accept.go index 200ccb7c7..b430b20d6 100644 --- a/rfqmsg/buy_accept.go +++ b/rfqmsg/buy_accept.go @@ -1,161 +1,19 @@ package rfqmsg import ( - "bytes" "fmt" - "io" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) -const ( - // Buy accept message type field TLV types. - - TypeBuyAcceptVersion tlv.Type = 0 - TypeBuyAcceptID tlv.Type = 2 - TypeBuyAcceptAskPrice tlv.Type = 4 - TypeBuyAcceptExpiry tlv.Type = 6 - TypeBuyAcceptSignature tlv.Type = 8 -) - -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 - - return tlv.MakeStaticRecord( - TypeBuyAcceptID, id, recordSize, IdEncoder, IdDecoder, - ) -} - -func TypeRecordBuyAcceptAskPrice(askPrice *lnwire.MilliSatoshi) tlv.Record { - return tlv.MakeStaticRecord( - TypeBuyAcceptAskPrice, askPrice, 8, milliSatoshiEncoder, - milliSatoshiDecoder, - ) -} - -func milliSatoshiEncoder(w io.Writer, val interface{}, buf *[8]byte) error { - if ms, ok := val.(*lnwire.MilliSatoshi); ok { - msUint64 := uint64(*ms) - return tlv.EUint64(w, &msUint64, buf) - } - - return tlv.NewTypeForEncodingErr(val, "MilliSatoshi") -} - -func milliSatoshiDecoder(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if ms, ok := val.(*lnwire.MilliSatoshi); ok { - var msInt uint64 - err := tlv.DUint64(r, &msInt, buf, l) - if err != nil { - return err - } - - *ms = lnwire.MilliSatoshi(msInt) - return nil - } - - return tlv.NewTypeForDecodingErr(val, "MilliSatoshi", l, 8) -} - -func TypeRecordBuyAcceptExpiry(expirySeconds *uint64) tlv.Record { - return tlv.MakePrimitiveRecord(TypeBuyAcceptExpiry, expirySeconds) -} - -func TypeRecordBuyAcceptSig(sig *[64]byte) tlv.Record { - return tlv.MakePrimitiveRecord(TypeBuyAcceptSignature, sig) -} - 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 - - // AskPrice is the asking price of the quote in milli-satoshis per asset - // unit. - AskPrice lnwire.MilliSatoshi - - // Expiry is the asking price expiry lifetime unix timestamp. - Expiry uint64 - - // sig is a signature over the serialized contents of the message. - sig [64]byte -} - -// encodeRecords provides all TLV records for encoding. -func (q *buyAcceptMsgData) encodeRecords() []tlv.Record { - return []tlv.Record{ - TypeRecordBuyAcceptVersion(&q.Version), - TypeRecordBuyAcceptID(&q.ID), - TypeRecordBuyAcceptAskPrice(&q.AskPrice), - TypeRecordBuyAcceptExpiry(&q.Expiry), - TypeRecordBuyAcceptSig(&q.sig), - } -} - -// 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), - TypeRecordBuyAcceptSig(&q.sig), - } -} - -// Encode encodes the structure into a TLV stream. -func (q *buyAcceptMsgData) Encode(writer io.Writer) error { - stream, err := tlv.NewStream(q.encodeRecords()...) - if err != nil { - return err - } - return stream.Encode(writer) -} - -// Decode decodes the structure from a TLV stream. -func (q *buyAcceptMsgData) Decode(r io.Reader) error { - stream, err := tlv.NewStream(q.decodeRecords()...) - if err != nil { - return err - } - return stream.DecodeP2P(r) -} - -// Bytes encodes the structure into a TLV stream and returns the bytes. -func (q *buyAcceptMsgData) Bytes() ([]byte, error) { - var b bytes.Buffer - err := q.Encode(&b) - if err != nil { - return nil, err - } - - return b.Bytes(), nil -} - // BuyAccept is a struct that represents a buy quote request accept message. type BuyAccept struct { // Peer is the peer that sent the quote request. @@ -172,8 +30,22 @@ type BuyAccept struct { // the request. AssetAmount uint64 - // buyAcceptMsgData is the message data for the quote accept message. - buyAcceptMsgData + // 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 + + // AskPrice is the asking price of the quote in milli-satoshis per asset + // unit. + AskPrice lnwire.MilliSatoshi + + // Expiry is the asking price expiry lifetime unix timestamp. + Expiry uint64 + + // sig is a signature over the serialized contents of the message. + sig [64]byte } // NewBuyAcceptFromRequest creates a new instance of a quote accept message @@ -185,12 +57,10 @@ func NewBuyAcceptFromRequest(request BuyRequest, askPrice lnwire.MilliSatoshi, Peer: request.Peer, AssetAmount: request.AssetAmount, Request: request, - buyAcceptMsgData: buyAcceptMsgData{ - Version: latestBuyAcceptVersion, - ID: request.ID, - AskPrice: askPrice, - Expiry: expiry, - }, + Version: latestBuyAcceptVersion, + ID: request.ID, + AskPrice: askPrice, + Expiry: expiry, } } @@ -216,14 +86,12 @@ func newBuyAcceptFromWireMsg(wireMsg WireMessage, ) return &BuyAccept{ - Peer: wireMsg.Peer, - buyAcceptMsgData: buyAcceptMsgData{ - Version: msgData.Version.Val, - ID: msgData.ID.Val, - Expiry: msgData.Expiry.Val, - sig: msgData.Sig.Val, - AskPrice: askPrice, - }, + Peer: wireMsg.Peer, + Version: msgData.Version.Val, + ID: msgData.ID.Val, + Expiry: msgData.Expiry.Val, + sig: msgData.Sig.Val, + AskPrice: askPrice, }, nil } diff --git a/rfqmsg/buy_accept_test.go b/rfqmsg/buy_accept_test.go index 1b79f99a2..5f195b1d8 100644 --- a/rfqmsg/buy_accept_test.go +++ b/rfqmsg/buy_accept_test.go @@ -1,12 +1,10 @@ package rfqmsg import ( - "bytes" "encoding/binary" "math/rand" "testing" - "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/internal/test" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -30,9 +28,7 @@ func TestAcceptShortChannelId(t *testing.T) { // Create an accept message. acceptMsg := BuyAccept{ - buyAcceptMsgData: buyAcceptMsgData{ - ID: id, - }, + ID: id, } // Derive the short channel ID from the accept message. @@ -42,67 +38,3 @@ func TestAcceptShortChannelId(t *testing.T) { // short channel ID. require.Equal(t, scidInt, uint64(actualScidInt)) } - -// TestBuyAcceptMsgDataEncodeDecode tests the encoding and decoding of a buy -// accept message. -func TestBuyAcceptMsgDataEncodeDecode(t *testing.T) { - t.Parallel() - - // Create a random ID. - randomIdBytes := test.RandBytes(32) - id := ID(randomIdBytes) - - // Create a random signature. - randomSigBytes := test.RandBytes(64) - var signature [64]byte - copy(signature[:], randomSigBytes[:]) - - testCases := []struct { - testName string - - version WireMsgDataVersion - id ID - askPrice lnwire.MilliSatoshi - expiry uint64 - sig [64]byte - assetID *asset.ID - }{ - { - testName: "all fields populated with basic values", - version: latestBuyAcceptVersion, - id: id, - askPrice: 1000, - expiry: 42000, - sig: signature, - assetID: &asset.ID{1, 2, 3}, - }, - { - testName: "empty fields", - }, - } - - 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, - sig: tc.sig, - } - - // Encode the message. - reqBytes, err := msg.Bytes() - require.NoError(tt, err, "unable to encode message") - - // Decode the message. - decodedMsg := buyAcceptMsgData{} - err = decodedMsg.Decode(bytes.NewReader(reqBytes)) - require.NoError(tt, err, "unable to decode message") - - // Assert that the decoded message is equal to the - // original message. - require.Equal(tt, msg, decodedMsg) - }) - } -} From 8107ab30ada548f819de625a1f0281dc01e51122 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 7 Jun 2024 16:23:13 +0100 Subject: [PATCH 5/6] multi: remove redundant field `AssetAmount` from `rfqmsg.BuyAccept` The `AssetAmount` field is redundant as it is a copy of the value found in `rfqmsg.BuyAccept.Request.AssetAmount`. It is therefore removed in this commit. --- rfq/negotiator.go | 2 +- rfq/order.go | 2 +- rfqmsg/buy_accept.go | 20 ++++++-------------- rpcserver.go | 2 +- taprpc/marshal.go | 2 +- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/rfq/negotiator.go b/rfq/negotiator.go index ee52468d3..9010795cc 100644 --- a/rfq/negotiator.go +++ b/rfq/negotiator.go @@ -610,7 +610,7 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept, // by the price oracle with the ask price provided by the peer. oraclePrice, _, err := n.queryAskFromPriceOracle( &msg.Peer, msg.Request.AssetID, nil, - msg.AssetAmount, nil, + msg.Request.AssetAmount, nil, ) if err != nil { // The price oracle returned an error. We will return diff --git a/rfq/order.go b/rfq/order.go index c55b02568..f3f119024 100644 --- a/rfq/order.go +++ b/rfq/order.go @@ -98,7 +98,7 @@ type AssetSalePolicy struct { func NewAssetSalePolicy(quote rfqmsg.BuyAccept) *AssetSalePolicy { return &AssetSalePolicy{ ID: quote.ID, - MaxAssetAmount: quote.AssetAmount, + MaxAssetAmount: quote.Request.AssetAmount, AskPrice: quote.AskPrice, expiry: quote.Expiry, assetID: quote.Request.AssetID, diff --git a/rfqmsg/buy_accept.go b/rfqmsg/buy_accept.go index b430b20d6..edfdf7bbf 100644 --- a/rfqmsg/buy_accept.go +++ b/rfqmsg/buy_accept.go @@ -23,13 +23,6 @@ type BuyAccept struct { // This field is not included in the wire message. Request BuyRequest - // AssetAmount is the amount of the asset that the accept message - // is for. - // - // TODO(ffranr): Remove this field in favour of the asset amount from - // the request. - AssetAmount uint64 - // Version is the version of the message data. Version WireMsgDataVersion @@ -54,13 +47,12 @@ func NewBuyAcceptFromRequest(request BuyRequest, askPrice lnwire.MilliSatoshi, expiry uint64) *BuyAccept { return &BuyAccept{ - Peer: request.Peer, - AssetAmount: request.AssetAmount, - Request: request, - Version: latestBuyAcceptVersion, - ID: request.ID, - AskPrice: askPrice, - Expiry: expiry, + Peer: request.Peer, + Request: request, + Version: latestBuyAcceptVersion, + ID: request.ID, + AskPrice: askPrice, + Expiry: expiry, } } diff --git a/rpcserver.go b/rpcserver.go index e6962143a..3c12a1517 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6274,7 +6274,7 @@ func marshalPeerAcceptedBuyQuotes( Peer: quote.Peer.String(), Id: quote.ID[:], Scid: uint64(scid), - AssetAmount: quote.AssetAmount, + AssetAmount: quote.Request.AssetAmount, AskPrice: uint64(quote.AskPrice), Expiry: quote.Expiry, } diff --git a/taprpc/marshal.go b/taprpc/marshal.go index bbf7c9dc2..b3892ab85 100644 --- a/taprpc/marshal.go +++ b/taprpc/marshal.go @@ -529,7 +529,7 @@ func MarshalAcceptedBuyQuoteEvent( Peer: event.Peer.String(), Id: event.ID[:], Scid: uint64(event.ShortChannelId()), - AssetAmount: event.AssetAmount, + AssetAmount: event.Request.AssetAmount, AskPrice: uint64(event.AskPrice), Expiry: event.Expiry, } From 8bcf46ebd23771b54e21ab34c6c595c31a0bce48 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 7 Jun 2024 16:26:32 +0100 Subject: [PATCH 6/6] multi: remove redundant field `AssetAmount` from `rfqmsg.SellAccept` The `AssetAmount` field is redundant as it is a copy of the value found in `rfqmsg.SellAccept.Request.AssetAmount`. It is therefore removed in this commit. --- rfq/negotiator.go | 3 ++- rfq/order.go | 2 +- rfqmsg/sell_accept.go | 20 ++++++-------------- rpcserver.go | 2 +- taprpc/marshal.go | 2 +- 5 files changed, 11 insertions(+), 18 deletions(-) diff --git a/rfq/negotiator.go b/rfq/negotiator.go index 9010795cc..97b414458 100644 --- a/rfq/negotiator.go +++ b/rfq/negotiator.go @@ -730,7 +730,8 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept, // for a bid price. We will then compare the bid price returned // by the price oracle with the bid price provided by the peer. oraclePrice, _, err := n.queryBidFromPriceOracle( - msg.Peer, msg.Request.AssetID, nil, msg.AssetAmount, + msg.Peer, msg.Request.AssetID, nil, + msg.Request.AssetAmount, ) if err != nil { // The price oracle returned an error. We will return diff --git a/rfq/order.go b/rfq/order.go index f3f119024..7024d3616 100644 --- a/rfq/order.go +++ b/rfq/order.go @@ -208,7 +208,7 @@ func NewAssetPurchasePolicy(quote rfqmsg.SellAccept) *AssetPurchasePolicy { return &AssetPurchasePolicy{ scid: quote.ShortChannelId(), AcceptedQuoteId: quote.ID, - AssetAmount: quote.AssetAmount, + AssetAmount: quote.Request.AssetAmount, BidPrice: quote.BidPrice, expiry: quote.Expiry, } diff --git a/rfqmsg/sell_accept.go b/rfqmsg/sell_accept.go index 0395575dd..1a25360b8 100644 --- a/rfqmsg/sell_accept.go +++ b/rfqmsg/sell_accept.go @@ -24,13 +24,6 @@ type SellAccept struct { // This field is not included in the wire message. Request SellRequest - // AssetAmount is the amount of the asset that the accept message - // is for. - // - // TODO(ffranr): Remove this field in favour of the asset amount from - // the request. - AssetAmount uint64 - // Version is the version of the message data. Version WireMsgDataVersion @@ -55,13 +48,12 @@ func NewSellAcceptFromRequest(request SellRequest, bidPrice lnwire.MilliSatoshi, expiry uint64) *SellAccept { return &SellAccept{ - Peer: request.Peer, - AssetAmount: request.AssetAmount, - Request: request, - Version: latestSellAcceptVersion, - ID: request.ID, - BidPrice: bidPrice, - Expiry: expiry, + Peer: request.Peer, + Request: request, + Version: latestSellAcceptVersion, + ID: request.ID, + BidPrice: bidPrice, + Expiry: expiry, } } diff --git a/rpcserver.go b/rpcserver.go index 3c12a1517..2f6b4ac74 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6297,7 +6297,7 @@ func marshalPeerAcceptedSellQuotes( Peer: quote.Peer.String(), Id: quote.ID[:], Scid: uint64(scid), - AssetAmount: quote.AssetAmount, + AssetAmount: quote.Request.AssetAmount, BidPrice: uint64(quote.BidPrice), Expiry: quote.Expiry, } diff --git a/taprpc/marshal.go b/taprpc/marshal.go index b3892ab85..d7618fe4c 100644 --- a/taprpc/marshal.go +++ b/taprpc/marshal.go @@ -514,7 +514,7 @@ func MarshalAcceptedSellQuoteEvent( Peer: event.Peer.String(), Id: event.ID[:], Scid: uint64(event.ShortChannelId()), - AssetAmount: event.AssetAmount, + AssetAmount: event.Request.AssetAmount, BidPrice: uint64(event.BidPrice), Expiry: event.Expiry, }