diff --git a/buffer/read.go b/buffer/read.go index 57050d58f..f38ab971e 100644 --- a/buffer/read.go +++ b/buffer/read.go @@ -7,7 +7,7 @@ import ( // ReadSize represents the size of the maximum message that can be read off the // wire by brontide. The buffer is used to hold the ciphertext while the // brontide state machine decrypts the message. -const ReadSize = lnwire.MaxMessagePayload + 16 +const ReadSize = lnwire.MaxSliceLength + 16 // Read is a static byte array sized to the maximum-allowed Lightning message // size, plus 16 bytes for the MAC. diff --git a/buffer/write.go b/buffer/write.go index 47010c29b..712571b97 100644 --- a/buffer/write.go +++ b/buffer/write.go @@ -7,7 +7,7 @@ import ( // WriteSize represents the size of the maximum plaintext message than can be // sent using brontide. The buffer does not include extra space for the MAC, as // that is applied by the Noise protocol after encrypting the plaintext. -const WriteSize = lnwire.MaxMessagePayload +const WriteSize = lnwire.MaxSliceLength // Write is static byte array occupying to maximum-allowed plaintext-message // size. diff --git a/chanbackup/single.go b/chanbackup/single.go index e51700c88..3007559e0 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -265,8 +265,15 @@ func (s *Single) Serialize(w io.Writer) error { return err } + // TODO(yy): remove the type assertion when we finished refactoring db + // into using write buffer. + buf, ok := w.(*bytes.Buffer) + if !ok { + return fmt.Errorf("expect io.Writer to be *bytes.Buffer") + } + return lnwire.WriteElements( - w, + buf, byte(s.Version), uint16(len(singleBytes.Bytes())), singleBytes.Bytes(), diff --git a/channeldb/waitingproof.go b/channeldb/waitingproof.go index b35586bf0..e8a09b758 100644 --- a/channeldb/waitingproof.go +++ b/channeldb/waitingproof.go @@ -2,6 +2,7 @@ package channeldb import ( "encoding/binary" + "fmt" "sync" "io" @@ -232,7 +233,14 @@ func (p *WaitingProof) Encode(w io.Writer) error { return err } - if err := p.AnnounceSignatures.Encode(w, 0); err != nil { + // TODO(yy): remove the type assertion when we finished refactoring db + // into using write buffer. + buf, ok := w.(*bytes.Buffer) + if !ok { + return fmt.Errorf("expect io.Writer to be *bytes.Buffer") + } + + if err := p.AnnounceSignatures.Encode(buf, 0); err != nil { return err } diff --git a/docs/release-notes/release-notes-0.14.0.md b/docs/release-notes/release-notes-0.14.0.md index 0ca255174..81db3f0cf 100644 --- a/docs/release-notes/release-notes-0.14.0.md +++ b/docs/release-notes/release-notes-0.14.0.md @@ -121,6 +121,10 @@ you. * [Update MC store in blocks](https://github.com/lightningnetwork/lnd/pull/5515) to make payment throughput better when using etcd. +* [The `lnwire` package now uses a write buffer pool](https://github.com/lightningnetwork/lnd/pull/4884) + when encoding/decoding messages. Such that most of the heap escapes are fixed, + resulting in less memory being used when running `lnd`. + ## Bug Fixes A bug has been fixed that would cause `lnd` to [try to bootstrap using the diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index f88a1a08f..f5786b918 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "fmt" "io" @@ -115,7 +116,7 @@ var _ Message = (*AcceptChannel)(nil) // protocol version. // // This is part of the lnwire.Message interface. -func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error { +func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error { // Since the upfront script is encoded as a TLV record, concatenate it // with the ExtraData, and write them as one. tlvRecords, err := packShutdownScript( @@ -125,23 +126,63 @@ func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error { return err } - return WriteElements(w, - a.PendingChannelID[:], - a.DustLimit, - a.MaxValueInFlight, - a.ChannelReserve, - a.HtlcMinimum, - a.MinAcceptDepth, - a.CsvDelay, - a.MaxAcceptedHTLCs, - a.FundingKey, - a.RevocationPoint, - a.PaymentPoint, - a.DelayedPaymentPoint, - a.HtlcPoint, - a.FirstCommitmentPoint, - tlvRecords, - ) + if err := WriteBytes(w, a.PendingChannelID[:]); err != nil { + return err + } + + if err := WriteSatoshi(w, a.DustLimit); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, a.MaxValueInFlight); err != nil { + return err + } + + if err := WriteSatoshi(w, a.ChannelReserve); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, a.HtlcMinimum); err != nil { + return err + } + + if err := WriteUint32(w, a.MinAcceptDepth); err != nil { + return err + } + + if err := WriteUint16(w, a.CsvDelay); err != nil { + return err + } + + if err := WriteUint16(w, a.MaxAcceptedHTLCs); err != nil { + return err + } + + if err := WritePublicKey(w, a.FundingKey); err != nil { + return err + } + + if err := WritePublicKey(w, a.RevocationPoint); err != nil { + return err + } + + if err := WritePublicKey(w, a.PaymentPoint); err != nil { + return err + } + + if err := WritePublicKey(w, a.DelayedPaymentPoint); err != nil { + return err + } + + if err := WritePublicKey(w, a.HtlcPoint); err != nil { + return err + } + + if err := WritePublicKey(w, a.FirstCommitmentPoint); err != nil { + return err + } + + return WriteBytes(w, tlvRecords) } // Decode deserializes the serialized AcceptChannel stored in the passed diff --git a/lnwire/announcement_signatures.go b/lnwire/announcement_signatures.go index 41372db19..49b610611 100644 --- a/lnwire/announcement_signatures.go +++ b/lnwire/announcement_signatures.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -64,14 +65,24 @@ func (a *AnnounceSignatures) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (a *AnnounceSignatures) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - a.ChannelID, - a.ShortChannelID, - a.NodeSignature, - a.BitcoinSignature, - a.ExtraOpaqueData, - ) +func (a *AnnounceSignatures) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, a.ChannelID); err != nil { + return err + } + + if err := WriteShortChannelID(w, a.ShortChannelID); err != nil { + return err + } + + if err := WriteSig(w, a.NodeSignature); err != nil { + return err + } + + if err := WriteSig(w, a.BitcoinSignature); err != nil { + return err + } + + return WriteBytes(w, a.ExtraOpaqueData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/channel_announcement.go b/lnwire/channel_announcement.go index 865b7b52c..2b34c0f99 100644 --- a/lnwire/channel_announcement.go +++ b/lnwire/channel_announcement.go @@ -87,21 +87,52 @@ func (a *ChannelAnnouncement) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (a *ChannelAnnouncement) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - a.NodeSig1, - a.NodeSig2, - a.BitcoinSig1, - a.BitcoinSig2, - a.Features, - a.ChainHash[:], - a.ShortChannelID, - a.NodeID1, - a.NodeID2, - a.BitcoinKey1, - a.BitcoinKey2, - a.ExtraOpaqueData, - ) +func (a *ChannelAnnouncement) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteSig(w, a.NodeSig1); err != nil { + return err + } + + if err := WriteSig(w, a.NodeSig2); err != nil { + return err + } + + if err := WriteSig(w, a.BitcoinSig1); err != nil { + return err + } + + if err := WriteSig(w, a.BitcoinSig2); err != nil { + return err + } + + if err := WriteRawFeatureVector(w, a.Features); err != nil { + return err + } + + if err := WriteBytes(w, a.ChainHash[:]); err != nil { + return err + } + + if err := WriteShortChannelID(w, a.ShortChannelID); err != nil { + return err + } + + if err := WriteBytes(w, a.NodeID1[:]); err != nil { + return err + } + + if err := WriteBytes(w, a.NodeID2[:]); err != nil { + return err + } + + if err := WriteBytes(w, a.BitcoinKey1[:]); err != nil { + return err + } + + if err := WriteBytes(w, a.BitcoinKey2[:]); err != nil { + return err + } + + return WriteBytes(w, a.ExtraOpaqueData) } // MsgType returns the integer uniquely identifying this message type on the @@ -116,20 +147,40 @@ func (a *ChannelAnnouncement) MsgType() MessageType { // be signed. func (a *ChannelAnnouncement) DataToSign() ([]byte, error) { // We should not include the signatures itself. - var w bytes.Buffer - err := WriteElements(&w, - a.Features, - a.ChainHash[:], - a.ShortChannelID, - a.NodeID1, - a.NodeID2, - a.BitcoinKey1, - a.BitcoinKey2, - a.ExtraOpaqueData, - ) - if err != nil { + b := make([]byte, 0, MaxMsgBody) + buf := bytes.NewBuffer(b) + + if err := WriteRawFeatureVector(buf, a.Features); err != nil { return nil, err } - return w.Bytes(), nil + if err := WriteBytes(buf, a.ChainHash[:]); err != nil { + return nil, err + } + + if err := WriteShortChannelID(buf, a.ShortChannelID); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.NodeID1[:]); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.NodeID2[:]); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.BitcoinKey1[:]); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.BitcoinKey2[:]); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil { + return nil, err + } + + return buf.Bytes(), nil } diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go index ebf2fcc1f..0de16a365 100644 --- a/lnwire/channel_reestablish.go +++ b/lnwire/channel_reestablish.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/btcec" @@ -75,13 +76,16 @@ var _ Message = (*ChannelReestablish)(nil) // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (a *ChannelReestablish) Encode(w io.Writer, pver uint32) error { - err := WriteElements(w, - a.ChanID, - a.NextLocalCommitHeight, - a.RemoteCommitTailHeight, - ) - if err != nil { +func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, a.ChanID); err != nil { + return err + } + + if err := WriteUint64(w, a.NextLocalCommitHeight); err != nil { + return err + } + + if err := WriteUint64(w, a.RemoteCommitTailHeight); err != nil { return err } @@ -93,15 +97,18 @@ func (a *ChannelReestablish) Encode(w io.Writer, pver uint32) error { // // NOTE: This is here primarily for the quickcheck tests, in // practice, we'll always populate this field. - return WriteElements(w, a.ExtraData) + return WriteBytes(w, a.ExtraData) } // Otherwise, we'll write out the remaining elements. - return WriteElements(w, - a.LastRemoteCommitSecret[:], - a.LocalUnrevokedCommitPoint, - a.ExtraData, - ) + if err := WriteBytes(w, a.LastRemoteCommitSecret[:]); err != nil { + return err + } + + if err := WritePublicKey(w, a.LocalUnrevokedCommitPoint); err != nil { + return err + } + return WriteBytes(w, a.ExtraData) } // Decode deserializes a serialized ChannelReestablish stored in the passed diff --git a/lnwire/channel_update.go b/lnwire/channel_update.go index 87a7d30a1..7881f972f 100644 --- a/lnwire/channel_update.go +++ b/lnwire/channel_update.go @@ -159,33 +159,58 @@ func (a *ChannelUpdate) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (a *ChannelUpdate) Encode(w io.Writer, pver uint32) error { - err := WriteElements(w, - a.Signature, - a.ChainHash[:], - a.ShortChannelID, - a.Timestamp, - a.MessageFlags, - a.ChannelFlags, - a.TimeLockDelta, - a.HtlcMinimumMsat, - a.BaseFee, - a.FeeRate, - ) - if err != nil { +func (a *ChannelUpdate) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteSig(w, a.Signature); err != nil { + return err + } + + if err := WriteBytes(w, a.ChainHash[:]); err != nil { + return err + } + + if err := WriteShortChannelID(w, a.ShortChannelID); err != nil { + return err + } + + if err := WriteUint32(w, a.Timestamp); err != nil { + return err + } + + if err := WriteChanUpdateMsgFlags(w, a.MessageFlags); err != nil { + return err + } + + if err := WriteChanUpdateChanFlags(w, a.ChannelFlags); err != nil { + return err + } + + if err := WriteUint16(w, a.TimeLockDelta); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, a.HtlcMinimumMsat); err != nil { + return err + } + + if err := WriteUint32(w, a.BaseFee); err != nil { + return err + } + + if err := WriteUint32(w, a.FeeRate); err != nil { return err } // Now append optional fields if they are set. Currently, the only // optional field is max HTLC. if a.MessageFlags.HasMaxHtlc() { - if err := WriteElements(w, a.HtlcMaximumMsat); err != nil { + err := WriteMilliSatoshi(w, a.HtlcMaximumMsat) + if err != nil { return err } } // Finally, append any extra opaque data. - return a.ExtraOpaqueData.Encode(w) + return WriteBytes(w, a.ExtraOpaqueData) } // MsgType returns the integer uniquely identifying this message type on the @@ -199,36 +224,58 @@ func (a *ChannelUpdate) MsgType() MessageType { // DataToSign is used to retrieve part of the announcement message which should // be signed. func (a *ChannelUpdate) DataToSign() ([]byte, error) { - // We should not include the signatures itself. - var w bytes.Buffer - err := WriteElements(&w, - a.ChainHash[:], - a.ShortChannelID, - a.Timestamp, - a.MessageFlags, - a.ChannelFlags, - a.TimeLockDelta, - a.HtlcMinimumMsat, - a.BaseFee, - a.FeeRate, - ) - if err != nil { + b := make([]byte, 0, MaxMsgBody) + buf := bytes.NewBuffer(b) + if err := WriteBytes(buf, a.ChainHash[:]); err != nil { + return nil, err + } + + if err := WriteShortChannelID(buf, a.ShortChannelID); err != nil { + return nil, err + } + + if err := WriteUint32(buf, a.Timestamp); err != nil { + return nil, err + } + + if err := WriteChanUpdateMsgFlags(buf, a.MessageFlags); err != nil { + return nil, err + } + + if err := WriteChanUpdateChanFlags(buf, a.ChannelFlags); err != nil { + return nil, err + } + + if err := WriteUint16(buf, a.TimeLockDelta); err != nil { + return nil, err + } + + if err := WriteMilliSatoshi(buf, a.HtlcMinimumMsat); err != nil { + return nil, err + } + + if err := WriteUint32(buf, a.BaseFee); err != nil { + return nil, err + } + + if err := WriteUint32(buf, a.FeeRate); err != nil { return nil, err } // Now append optional fields if they are set. Currently, the only // optional field is max HTLC. if a.MessageFlags.HasMaxHtlc() { - if err := WriteElements(&w, a.HtlcMaximumMsat); err != nil { + err := WriteMilliSatoshi(buf, a.HtlcMaximumMsat) + if err != nil { return nil, err } } // Finally, append any extra opaque data. - if err := a.ExtraOpaqueData.Encode(&w); err != nil { + if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil { return nil, err } - return w.Bytes(), nil + return buf.Bytes(), nil } diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index 3e4dcdf26..9ba170ba5 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcutil" @@ -63,10 +64,20 @@ func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *ClosingSigned) Encode(w io.Writer, pver uint32) error { - return WriteElements( - w, c.ChannelID, c.FeeSatoshis, c.Signature, c.ExtraData, - ) +func (c *ClosingSigned) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChannelID); err != nil { + return err + } + + if err := WriteSatoshi(w, c.FeeSatoshis); err != nil { + return err + } + + if err := WriteSig(w, c.Signature); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go index 3251fe8dc..ca105f71a 100644 --- a/lnwire/commit_sig.go +++ b/lnwire/commit_sig.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -69,13 +70,20 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *CommitSig) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.CommitSig, - c.HtlcSigs, - c.ExtraData, - ) +func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteSig(w, c.CommitSig); err != nil { + return err + } + + if err := WriteSigs(w, c.HtlcSigs); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/error.go b/lnwire/error.go index 0aa2d4c31..74814b59e 100644 --- a/lnwire/error.go +++ b/lnwire/error.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "fmt" "io" ) @@ -103,11 +104,12 @@ func (c *Error) Decode(r io.Reader, pver uint32) error { // protocol version specified. // // This is part of the lnwire.Message interface. -func (c *Error) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.Data, - ) +func (c *Error) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteBytes(w, c.ChanID[:]); err != nil { + return err + } + + return WriteErrorData(w, c.Data) } // MsgType returns the integer uniquely identifying an Error message on the diff --git a/lnwire/extra_bytes.go b/lnwire/extra_bytes.go index 22fd20bd8..70554f4f5 100644 --- a/lnwire/extra_bytes.go +++ b/lnwire/extra_bytes.go @@ -16,9 +16,9 @@ import ( type ExtraOpaqueData []byte // Encode attempts to encode the raw extra bytes into the passed io.Writer. -func (e *ExtraOpaqueData) Encode(w io.Writer) error { +func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error { eBytes := []byte((*e)[:]) - if err := WriteElements(w, eBytes); err != nil { + if err := WriteBytes(w, eBytes); err != nil { return err } diff --git a/lnwire/features.go b/lnwire/features.go index 9dfa07ee1..675ba0f50 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -91,7 +91,7 @@ const ( // attacks on the receiver of a payment. PaymentAddrOptional FeatureBit = 15 - // MPPOptional is a required feature bit that signals that the receiver + // MPPRequired is a required feature bit that signals that the receiver // of a payment requires settlement of an invoice with more than one // HTLC. MPPRequired FeatureBit = 16 @@ -124,7 +124,7 @@ const ( // transactions, which also imply anchor commitments. AnchorsZeroFeeHtlcTxRequired FeatureBit = 22 - // AnchorsZeroFeeHtlcTxRequired is an optional feature bit that signals + // AnchorsZeroFeeHtlcTxOptional is an optional feature bit that signals // that the node supports channels having zero-fee second-level HTLC // transactions, which also imply anchor commitments. AnchorsZeroFeeHtlcTxOptional FeatureBit = 23 diff --git a/lnwire/funding_created.go b/lnwire/funding_created.go index 0f10214ed..02b513471 100644 --- a/lnwire/funding_created.go +++ b/lnwire/funding_created.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/wire" @@ -40,11 +41,20 @@ var _ Message = (*FundingCreated)(nil) // protocol version. // // This is part of the lnwire.Message interface. -func (f *FundingCreated) Encode(w io.Writer, pver uint32) error { - return WriteElements( - w, f.PendingChannelID[:], f.FundingPoint, f.CommitSig, - f.ExtraData, - ) +func (f *FundingCreated) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteBytes(w, f.PendingChannelID[:]); err != nil { + return err + } + + if err := WriteOutPoint(w, f.FundingPoint); err != nil { + return err + } + + if err := WriteSig(w, f.CommitSig); err != nil { + return err + } + + return WriteBytes(w, f.ExtraData) } // Decode deserializes the serialized FundingCreated stored in the passed diff --git a/lnwire/funding_locked.go b/lnwire/funding_locked.go index 0f8e4ca1e..bdb0a9b06 100644 --- a/lnwire/funding_locked.go +++ b/lnwire/funding_locked.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/btcec" @@ -58,12 +59,16 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { // protocol version. // // This is part of the lnwire.Message interface. -func (c *FundingLocked) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.NextPerCommitmentPoint, - c.ExtraData, - ) +func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WritePublicKey(w, c.NextPerCommitmentPoint); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the uint32 code which uniquely identifies this message as a diff --git a/lnwire/funding_signed.go b/lnwire/funding_signed.go index 15c62cf05..d7386f2ed 100644 --- a/lnwire/funding_signed.go +++ b/lnwire/funding_signed.go @@ -1,6 +1,9 @@ package lnwire -import "io" +import ( + "bytes" + "io" +) // FundingSigned is sent from Bob (the responder) to Alice (the initiator) // after receiving the funding outpoint and her signature for Bob's version of @@ -29,8 +32,16 @@ var _ Message = (*FundingSigned)(nil) // protocol version. // // This is part of the lnwire.Message interface. -func (f *FundingSigned) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, f.ChanID, f.CommitSig, f.ExtraData) +func (f *FundingSigned) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, f.ChanID); err != nil { + return err + } + + if err := WriteSig(w, f.CommitSig); err != nil { + return err + } + + return WriteBytes(w, f.ExtraData) } // Decode deserializes the serialized FundingSigned stored in the passed diff --git a/lnwire/gossip_timestamp_range.go b/lnwire/gossip_timestamp_range.go index 6827b9818..d2dbddecb 100644 --- a/lnwire/gossip_timestamp_range.go +++ b/lnwire/gossip_timestamp_range.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -57,13 +58,20 @@ func (g *GossipTimestampRange) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (g *GossipTimestampRange) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - g.ChainHash[:], - g.FirstTimestamp, - g.TimestampRange, - g.ExtraData, - ) +func (g *GossipTimestampRange) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteBytes(w, g.ChainHash[:]); err != nil { + return err + } + + if err := WriteUint32(w, g.FirstTimestamp); err != nil { + return err + } + + if err := WriteUint32(w, g.TimestampRange); err != nil { + return err + } + + return WriteBytes(w, g.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/init_message.go b/lnwire/init_message.go index 18fa693b4..dbbddea2b 100644 --- a/lnwire/init_message.go +++ b/lnwire/init_message.go @@ -1,6 +1,9 @@ package lnwire -import "io" +import ( + "bytes" + "io" +) // Init is the first message reveals the features supported or required by this // node. Nodes wait for receipt of the other's features to simplify error @@ -56,12 +59,16 @@ func (msg *Init) Decode(r io.Reader, pver uint32) error { // the protocol version specified. // // This is part of the lnwire.Message interface. -func (msg *Init) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - msg.GlobalFeatures, - msg.Features, - msg.ExtraData, - ) +func (msg *Init) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteRawFeatureVector(w, msg.GlobalFeatures); err != nil { + return err + } + + if err := WriteRawFeatureVector(w, msg.Features); err != nil { + return err + } + + return WriteBytes(w, msg.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index c180cad38..20edcbab4 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -75,13 +75,11 @@ func (a addressType) AddrLen() uint16 { } // WriteElement is a one-stop shop to write the big endian representation of -// any element which is to be serialized for the wire protocol. The passed -// io.Writer should be backed by an appropriately sized byte slice, or be able -// to dynamically expand to accommodate additional data. +// any element which is to be serialized for the wire protocol. // -// TODO(roasbeef): this should eventually draw from a buffer pool for -// serialization. -func WriteElement(w io.Writer, element interface{}) error { +// TODO(yy): rm this method once we finish dereferencing it from other +// packages. +func WriteElement(w *bytes.Buffer, element interface{}) error { switch e := element.(type) { case NodeAlias: if _, err := w.Write(e[:]); err != nil { @@ -437,10 +435,13 @@ func WriteElement(w io.Writer, element interface{}) error { } // WriteElements is writes each element in the elements slice to the passed -// io.Writer using WriteElement. -func WriteElements(w io.Writer, elements ...interface{}) error { +// buffer using WriteElement. +// +// TODO(yy): rm this method once we finish dereferencing it from other +// packages. +func WriteElements(buf *bytes.Buffer, elements ...interface{}) error { for _, element := range elements { - err := WriteElement(w, element) + err := WriteElement(buf, element) if err != nil { return err } @@ -828,8 +829,11 @@ func ReadElement(r io.Reader, element interface{}) error { length := binary.BigEndian.Uint16(addrLen[:]) var addrBytes [deliveryAddressMaxSize]byte + if length > deliveryAddressMaxSize { - return fmt.Errorf("cannot read %d bytes into addrBytes", length) + return fmt.Errorf( + "cannot read %d bytes into addrBytes", length, + ) } if _, err = io.ReadFull(r, addrBytes[:length]); err != nil { return err diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 14082bfe9..4475b3382 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -232,7 +232,7 @@ func TestMaxOutPointIndex(t *testing.T) { } var b bytes.Buffer - if err := WriteElement(&b, op); err == nil { + if err := WriteOutPoint(&b, op); err == nil { t.Fatalf("write of outPoint should fail, index exceeds 16-bits") } } diff --git a/lnwire/message.go b/lnwire/message.go index 048122887..ccd24d10b 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -12,10 +12,6 @@ import ( "io" ) -// MaxMessagePayload is the maximum bytes a message can be regardless of other -// individual limits imposed by messages themselves. -const MaxMessagePayload = 65535 // 65KB - // MessageType is the unique 2 byte big-endian integer that indicates the type // of message on the wire. All messages have a very simple header which // consists simply of 2-byte message type. We omit a length field, and checksum @@ -56,6 +52,27 @@ const ( MsgGossipTimestampRange = 265 ) +// ErrorEncodeMessage is used when failed to encode the message payload. +func ErrorEncodeMessage(err error) error { + return fmt.Errorf("failed to encode message to buffer, got %w", err) +} + +// ErrorWriteMessageType is used when failed to write the message type. +func ErrorWriteMessageType(err error) error { + return fmt.Errorf("failed to write message type, got %w", err) +} + +// ErrorPayloadTooLarge is used when the payload size exceeds the +// MaxMsgBody. +func ErrorPayloadTooLarge(size int) error { + return fmt.Errorf( + "message payload is too large - encoded %d bytes, "+ + "but maximum message payload is %d bytes", + size, MaxMsgBody, + ) + +} + // String return the string representation of message type. func (t MessageType) String() string { switch t { @@ -141,8 +158,8 @@ type Serializable interface { Decode(io.Reader, uint32) error // Encode converts object to the bytes stream and write it into the - // writer. - Encode(io.Writer, uint32) error + // write buffer. + Encode(*bytes.Buffer, uint32) error } // Message is an interface that defines a lightning wire protocol message. The @@ -222,44 +239,49 @@ func makeEmptyMessage(msgType MessageType) (Message, error) { return msg, nil } -// WriteMessage writes a lightning Message to w including the necessary header -// information and returns the number of bytes written. -func WriteMessage(w io.Writer, msg Message, pver uint32) (int, error) { - totalBytes := 0 +// WriteMessage writes a lightning Message to a buffer including the necessary +// header information and returns the number of bytes written. If any error is +// encountered, the buffer passed will be reset to its original state since we +// don't want any broken bytes left. In other words, no bytes will be written +// if there's an error. Either all or none of the message bytes will be written +// to the buffer. +// +// NOTE: this method is not concurrent safe. +func WriteMessage(buf *bytes.Buffer, msg Message, pver uint32) (int, error) { + // Record the size of the bytes already written in buffer. + oldByteSize := buf.Len() - // Encode the message payload itself into a temporary buffer. - // TODO(roasbeef): create buffer pool - var bw bytes.Buffer - if err := msg.Encode(&bw, pver); err != nil { - return totalBytes, err - } - payload := bw.Bytes() - lenp := len(payload) - - // Enforce maximum message payload, which means the body cannot be - // greater than MaxMsgBody. - if lenp > MaxMsgBody { - return totalBytes, fmt.Errorf("message payload is too large - "+ - "encoded %d bytes, but maximum message body is %d bytes", - lenp, MaxMsgBody) + // cleanBrokenBytes is a helper closure that helps reset the buffer to + // its original state. It truncates all the bytes written in current + // scope. + var cleanBrokenBytes = func(b *bytes.Buffer) int { + b.Truncate(oldByteSize) + return 0 } - // With the initial sanity checks complete, we'll now write out the - // message type itself. + // Write the message type. var mType [2]byte binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType())) - n, err := w.Write(mType[:]) - totalBytes += n + msgTypeBytes, err := buf.Write(mType[:]) if err != nil { - return totalBytes, err + return cleanBrokenBytes(buf), ErrorWriteMessageType(err) } - // With the message type written, we'll now write out the raw payload - // itself. - n, err = w.Write(payload) - totalBytes += n + // Use the write buffer to encode our message. + if err := msg.Encode(buf, pver); err != nil { + return cleanBrokenBytes(buf), ErrorEncodeMessage(err) + } - return totalBytes, err + // Enforce maximum overall message payload. The write buffer now has + // the size of len(originalBytes) + len(payload) + len(type). We want + // to enforce the payload here, so we subtract it by the length of the + // type and old bytes. + lenp := buf.Len() - oldByteSize - msgTypeBytes + if lenp > MaxMsgBody { + return cleanBrokenBytes(buf), ErrorPayloadTooLarge(lenp) + } + + return buf.Len() - oldByteSize, nil } // ReadMessage reads, validates, and parses the next Lightning message from r diff --git a/lnwire/message_test.go b/lnwire/message_test.go new file mode 100644 index 000000000..55e0c0153 --- /dev/null +++ b/lnwire/message_test.go @@ -0,0 +1,1011 @@ +package lnwire_test + +import ( + "bytes" + "encoding/binary" + "errors" + "image/color" + "io" + "math" + "math/big" + "math/rand" + "net" + "sync" + "testing" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tor" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +const deliveryAddressMaxSize = 34 +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +var ( + testSig = &btcec.Signature{ + R: new(big.Int), + S: new(big.Int), + } + testNodeSig, _ = lnwire.NewSigFromSignature(testSig) + + testNumExtraBytes = 1000 + testNumSigs = 100 + testNumChanIDs = 1000 + buffer = make([]byte, 0, lnwire.MaxSliceLength) + + bufPool = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(buffer) + }, + } +) + +type mockMsg struct { + mock.Mock +} + +func (m *mockMsg) Decode(r io.Reader, pver uint32) error { + args := m.Called(r, pver) + return args.Error(0) +} + +func (m *mockMsg) Encode(w *bytes.Buffer, pver uint32) error { + args := m.Called(w, pver) + return args.Error(0) +} + +func (m *mockMsg) MsgType() lnwire.MessageType { + args := m.Called() + return lnwire.MessageType(args.Int(0)) +} + +// A compile time check to ensure mockMsg implements the lnwire.Message +// interface. +var _ lnwire.Message = (*mockMsg)(nil) + +// TestWriteMessage tests the function lnwire.WriteMessage. +func TestWriteMessage(t *testing.T) { + var ( + buf = new(bytes.Buffer) + + // encodeNormalSize specifies a message size that is normal. + encodeNormalSize = 1000 + + // encodeOversize specifies a message size that's too big. + encodeOversize = lnwire.MaxMsgBody + 1 + + // errDummy is returned by the msg.Encode when specified. + errDummy = errors.New("test error") + + // oneByte is a dummy byte used to fill up the buffer. + oneByte = [1]byte{} + ) + + testCases := []struct { + name string + + // encodeSize controls how many bytes are written to the buffer + // by the method msg.Encode(buf, pver). + encodeSize int + + // encodeErr determines the return value of the method + // msg.Encode(buf, pver). + encodeErr error + + errorExpected error + }{ + + { + name: "successful write", + encodeSize: encodeNormalSize, + encodeErr: nil, + errorExpected: nil, + }, + { + name: "failed to encode payload", + encodeSize: encodeNormalSize, + encodeErr: errDummy, + errorExpected: lnwire.ErrorEncodeMessage(errDummy), + }, + { + name: "exceeds MaxMsgBody", + encodeSize: encodeOversize, + encodeErr: nil, + errorExpected: lnwire.ErrorPayloadTooLarge( + encodeOversize, + ), + }, + } + + for _, test := range testCases { + tc := test + t.Run(tc.name, func(t *testing.T) { + // Start the test by creating a mock message and patch + // the relevant methods. + msg := &mockMsg{} + + // Use message type Ping here since all types are + // encoded using 2 bytes, it won't affect anything + // here. + msg.On("MsgType").Return(lnwire.MsgPing) + + // Encode will return the specified error (could be + // nil) and has the side effect of filling up the + // buffer by repeating the oneByte encodeSize times. + msg.On("Encode", mock.Anything, mock.Anything).Return( + tc.encodeErr, + ).Run(func(_ mock.Arguments) { + for i := 0; i < tc.encodeSize; i++ { + _, err := buf.Write(oneByte[:]) + require.NoError(t, err) + } + }) + + // Record the initial state of the buffer and write the + // message. + oldBytesSize := buf.Len() + bytesWritten, err := lnwire.WriteMessage( + buf, msg, 1, + ) + + // Check that the returned error is expected. + require.Equal( + t, tc.errorExpected, err, "unexpected err", + ) + + // If there's an error, no bytes should be written to + // the buf. + if tc.errorExpected != nil { + require.Equal( + t, 0, bytesWritten, + "bytes written should be 0", + ) + + // We also check that the old buf was not + // affected. + require.Equal( + t, oldBytesSize, buf.Len(), + "original buffer should not change", + ) + } else { + expected := buf.Len() - oldBytesSize + require.Equal( + t, expected, bytesWritten, + "bytes written not matched", + ) + } + + // Finally, check the mocked methods are called as + // expected. + msg.AssertExpectations(t) + }) + } +} + +// BenchmarkWriteMessage benchmarks the performance of lnwire.WriteMessage. It +// generates a test message for each of the lnwire.Message, calls the +// WriteMessage method and benchmark it. +func BenchmarkWriteMessage(b *testing.B) { + // Create testing messages. We will use a constant seed to make sure + // the benchmark uses the same data every time. + r := rand.New(rand.NewSource(42)) + + msgAll := makeAllMessages(b, r) + + // Iterate all messages and write each once. + for _, msg := range msgAll { + m := msg + // Run each message as a sub benchmark test. + b.Run(msg.MsgType().String(), func(b *testing.B) { + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Fetch a buffer from the pool and reset it. + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + + _, err := lnwire.WriteMessage(buf, m, 0) + require.NoError(b, err, "unable to write msg") + + // Put the buffer back when done. + bufPool.Put(buf) + } + }) + } +} + +// BenchmarkReadMessage benchmarks the performance of lnwire.ReadMessage. It +// first creates a test message for each of the lnwire.Message, writes it to +// the buffer, then later reads it from the buffer. +func BenchmarkReadMessage(b *testing.B) { + // Create testing messages. We will use a constant seed to make sure + // the benchmark uses the same data every time. + r := rand.New(rand.NewSource(42)) + msgAll := makeAllMessages(b, r) + + // Write all the messages to the buffer. + for _, msg := range msgAll { + // Fetch a buffer from the pool and reset it. + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + + _, err := lnwire.WriteMessage(buf, msg, 0) + require.NoError(b, err, "unable to write msg") + + // Run each message as a sub benchmark test. + m := msg + b.Run(m.MsgType().String(), func(b *testing.B) { + b.ResetTimer() + + for i := 0; i < b.N; i++ { + r := bytes.NewBuffer(buf.Bytes()) + + // Read the message from the buffer. + _, err := lnwire.ReadMessage(r, 0) + require.NoError(b, err, "unable to read msg") + } + }) + + // Put the buffer back when done. + bufPool.Put(buf) + } +} + +// makeAllMessages is used to create testing messages for each lnwire message +// type. +// +// TODO(yy): the following testing messages are created somewhat arbitrary. We +// should standardlize each of the testing messages so that a better baseline +// can be used. +func makeAllMessages(t testing.TB, r *rand.Rand) []lnwire.Message { + msgAll := []lnwire.Message{} + + msgAll = append(msgAll, newMsgInit(t, r)) + msgAll = append(msgAll, newMsgError(t, r)) + msgAll = append(msgAll, newMsgPing(t, r)) + msgAll = append(msgAll, newMsgPong(t, r)) + msgAll = append(msgAll, newMsgOpenChannel(t, r)) + msgAll = append(msgAll, newMsgAcceptChannel(t, r)) + msgAll = append(msgAll, newMsgFundingCreated(t, r)) + msgAll = append(msgAll, newMsgFundingSigned(t, r)) + msgAll = append(msgAll, newMsgFundingLocked(t, r)) + msgAll = append(msgAll, newMsgShutdown(t, r)) + msgAll = append(msgAll, newMsgClosingSigned(t, r)) + msgAll = append(msgAll, newMsgUpdateAddHTLC(t, r)) + msgAll = append(msgAll, newMsgUpdateFulfillHTLC(t, r)) + msgAll = append(msgAll, newMsgUpdateFailHTLC(t, r)) + msgAll = append(msgAll, newMsgCommitSig(t, r)) + msgAll = append(msgAll, newMsgRevokeAndAck(t, r)) + msgAll = append(msgAll, newMsgUpdateFee(t, r)) + msgAll = append(msgAll, newMsgUpdateFailMalformedHTLC(t, r)) + msgAll = append(msgAll, newMsgChannelReestablish(t, r)) + msgAll = append(msgAll, newMsgChannelAnnouncement(t, r)) + msgAll = append(msgAll, newMsgNodeAnnouncement(t, r)) + msgAll = append(msgAll, newMsgChannelUpdate(t, r)) + msgAll = append(msgAll, newMsgAnnounceSignatures(t, r)) + msgAll = append(msgAll, newMsgQueryShortChanIDs(t, r)) + msgAll = append(msgAll, newMsgReplyShortChanIDsEnd(t, r)) + msgAll = append(msgAll, newMsgQueryChannelRange(t, r)) + msgAll = append(msgAll, newMsgReplyChannelRange(t, r)) + msgAll = append(msgAll, newMsgGossipTimestampRange(t, r)) + msgAll = append(msgAll, newMsgQueryShortChanIDsZlib(t, r)) + msgAll = append(msgAll, newMsgReplyChannelRangeZlib(t, r)) + + return msgAll +} + +func newMsgInit(t testing.TB, r io.Reader) *lnwire.Init { + t.Helper() + + return &lnwire.Init{ + GlobalFeatures: rawFeatureVector(), + Features: rawFeatureVector(), + ExtraData: createExtraData(t, r), + } +} + +// newMsgOpenChannel creates a testing OpenChannel message. +func newMsgOpenChannel(t testing.TB, r *rand.Rand) *lnwire.OpenChannel { + t.Helper() + + msg := &lnwire.OpenChannel{ + FundingAmount: btcutil.Amount(r.Int63()), + PushAmount: lnwire.MilliSatoshi(r.Int63()), + DustLimit: btcutil.Amount(r.Int63()), + MaxValueInFlight: lnwire.MilliSatoshi(r.Int63()), + ChannelReserve: btcutil.Amount(r.Int63()), + HtlcMinimum: lnwire.MilliSatoshi(r.Int63()), + FeePerKiloWeight: uint32(r.Int31()), + CsvDelay: uint16(r.Intn(1 << 16)), + MaxAcceptedHTLCs: uint16(r.Intn(1 << 16)), + ChannelFlags: lnwire.FundingFlag(uint8(r.Intn(1 << 8))), + FundingKey: randPubKey(t), + RevocationPoint: randPubKey(t), + PaymentPoint: randPubKey(t), + DelayedPaymentPoint: randPubKey(t), + HtlcPoint: randPubKey(t), + FirstCommitmentPoint: randPubKey(t), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read bytes for ChainHash") + + _, err = r.Read(msg.PendingChannelID[:]) + require.NoError(t, err, "unable to read bytes for PendingChannelID") + + return msg +} + +func newMsgAcceptChannel(t testing.TB, r *rand.Rand) *lnwire.AcceptChannel { + t.Helper() + + msg := &lnwire.AcceptChannel{ + DustLimit: btcutil.Amount(r.Int63()), + MaxValueInFlight: lnwire.MilliSatoshi(r.Int63()), + ChannelReserve: btcutil.Amount(r.Int63()), + MinAcceptDepth: uint32(r.Int31()), + HtlcMinimum: lnwire.MilliSatoshi(r.Int63()), + CsvDelay: uint16(r.Intn(1 << 16)), + MaxAcceptedHTLCs: uint16(r.Intn(1 << 16)), + FundingKey: randPubKey(t), + RevocationPoint: randPubKey(t), + PaymentPoint: randPubKey(t), + DelayedPaymentPoint: randPubKey(t), + HtlcPoint: randPubKey(t), + FirstCommitmentPoint: randPubKey(t), + UpfrontShutdownScript: randDeliveryAddress(t, r), + ExtraData: createExtraData(t, r), + } + _, err := r.Read(msg.PendingChannelID[:]) + require.NoError(t, err, "unable to generate pending chan id") + + return msg +} + +func newMsgError(t testing.TB, r io.Reader) *lnwire.Error { + t.Helper() + + msg := lnwire.NewError() + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + msg.Data = createExtraData(t, r) + + return msg +} + +func newMsgPing(t testing.TB, r *rand.Rand) *lnwire.Ping { + t.Helper() + + return &lnwire.Ping{ + NumPongBytes: uint16(r.Intn(1 << 16)), + PaddingBytes: createExtraData(t, r), + } +} + +func newMsgPong(t testing.TB, r io.Reader) *lnwire.Pong { + t.Helper() + + return &lnwire.Pong{ + PongBytes: createExtraData(t, r), + } +} + +func newMsgFundingCreated(t testing.TB, r *rand.Rand) *lnwire.FundingCreated { + t.Helper() + + msg := &lnwire.FundingCreated{ + CommitSig: testNodeSig, + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.PendingChannelID[:]) + require.NoError(t, err, "unable to generate pending chan id") + + _, err = r.Read(msg.FundingPoint.Hash[:]) + require.NoError(t, err, "unable to generate hash") + + msg.FundingPoint.Index = uint32(r.Int31()) % math.MaxUint16 + + return msg +} + +func newMsgFundingSigned(t testing.TB, r io.Reader) *lnwire.FundingSigned { + t.Helper() + + var c [32]byte + + _, err := r.Read(c[:]) + require.NoError(t, err, "unable to generate chan id") + + msg := &lnwire.FundingSigned{ + ChanID: lnwire.ChannelID(c), + CommitSig: testNodeSig, + ExtraData: createExtraData(t, r), + } + + return msg +} + +func newMsgFundingLocked(t testing.TB, r io.Reader) *lnwire.FundingLocked { + t.Helper() + + var c [32]byte + + _, err := r.Read(c[:]) + require.NoError(t, err, "unable to generate chan id") + + pubKey := randPubKey(t) + + msg := lnwire.NewFundingLocked(lnwire.ChannelID(c), pubKey) + msg.ExtraData = createExtraData(t, r) + + return msg +} + +func newMsgShutdown(t testing.TB, r *rand.Rand) *lnwire.Shutdown { + t.Helper() + + msg := &lnwire.Shutdown{ + Address: randDeliveryAddress(t, r), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChannelID[:]) + require.NoError(t, err, "unable to generate channel id") + + return msg +} + +func newMsgClosingSigned(t testing.TB, r *rand.Rand) *lnwire.ClosingSigned { + t.Helper() + + msg := &lnwire.ClosingSigned{ + FeeSatoshis: btcutil.Amount(r.Int63()), + Signature: testNodeSig, + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChannelID[:]) + require.NoError(t, err, "unable to generate chan id") + + return msg +} + +func newMsgUpdateAddHTLC(t testing.TB, r *rand.Rand) *lnwire.UpdateAddHTLC { + t.Helper() + + msg := &lnwire.UpdateAddHTLC{ + ID: r.Uint64(), + Amount: lnwire.MilliSatoshi(r.Int63()), + Expiry: r.Uint32(), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + _, err = r.Read(msg.PaymentHash[:]) + require.NoError(t, err, "unable to generate paymenthash") + + _, err = r.Read(msg.OnionBlob[:]) + require.NoError(t, err, "unable to generate onion blob") + + return msg +} + +func newMsgUpdateFulfillHTLC(t testing.TB, + r *rand.Rand) *lnwire.UpdateFulfillHTLC { + + t.Helper() + + msg := &lnwire.UpdateFulfillHTLC{ + ID: r.Uint64(), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + _, err = r.Read(msg.PaymentPreimage[:]) + require.NoError(t, err, "unable to generate payment preimage") + + return msg +} + +func newMsgUpdateFailHTLC(t testing.TB, r *rand.Rand) *lnwire.UpdateFailHTLC { + t.Helper() + + msg := &lnwire.UpdateFailHTLC{ + ID: r.Uint64(), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + return msg +} + +func newMsgCommitSig(t testing.TB, r io.Reader) *lnwire.CommitSig { + t.Helper() + + msg := lnwire.NewCommitSig() + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + msg.CommitSig = testNodeSig + msg.ExtraData = createExtraData(t, r) + + msg.HtlcSigs = make([]lnwire.Sig, testNumSigs) + for i := 0; i < testNumSigs; i++ { + msg.HtlcSigs[i] = testNodeSig + } + + return msg +} + +func newMsgRevokeAndAck(t testing.TB, r io.Reader) *lnwire.RevokeAndAck { + t.Helper() + + msg := lnwire.NewRevokeAndAck() + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + _, err = r.Read(msg.Revocation[:]) + require.NoError(t, err, "unable to generate bytes") + + msg.NextRevocationKey = randPubKey(t) + require.NoError(t, err, "unable to generate key") + + msg.ExtraData = createExtraData(t, r) + + return msg +} + +func newMsgUpdateFee(t testing.TB, r *rand.Rand) *lnwire.UpdateFee { + t.Helper() + + msg := &lnwire.UpdateFee{ + FeePerKw: uint32(r.Int31()), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + return msg +} + +func newMsgUpdateFailMalformedHTLC(t testing.TB, + r *rand.Rand) *lnwire.UpdateFailMalformedHTLC { + + t.Helper() + + msg := &lnwire.UpdateFailMalformedHTLC{ + ID: r.Uint64(), + FailureCode: lnwire.FailCode(r.Intn(1 << 16)), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.ChanID[:]) + require.NoError(t, err, "unable to generate chan id") + + _, err = r.Read(msg.ShaOnionBlob[:]) + require.NoError(t, err, "unable to generate sha256 onion blob") + + return msg +} + +func newMsgChannelReestablish(t testing.TB, + r *rand.Rand) *lnwire.ChannelReestablish { + + t.Helper() + + msg := &lnwire.ChannelReestablish{ + NextLocalCommitHeight: uint64(r.Int63()), + RemoteCommitTailHeight: uint64(r.Int63()), + LocalUnrevokedCommitPoint: randPubKey(t), + ExtraData: createExtraData(t, r), + } + + _, err := r.Read(msg.LastRemoteCommitSecret[:]) + require.NoError(t, err, "unable to read commit secret") + + return msg +} + +func newMsgChannelAnnouncement(t testing.TB, + r *rand.Rand) *lnwire.ChannelAnnouncement { + + t.Helper() + + msg := &lnwire.ChannelAnnouncement{ + ShortChannelID: lnwire.NewShortChanIDFromInt(uint64(r.Int63())), + Features: rawFeatureVector(), + NodeID1: randRawKey(t), + NodeID2: randRawKey(t), + BitcoinKey1: randRawKey(t), + BitcoinKey2: randRawKey(t), + ExtraOpaqueData: createExtraData(t, r), + NodeSig1: testNodeSig, + NodeSig2: testNodeSig, + BitcoinSig1: testNodeSig, + BitcoinSig2: testNodeSig, + } + + _, err := r.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to generate chain hash") + + return msg +} + +func newMsgNodeAnnouncement(t testing.TB, + r *rand.Rand) *lnwire.NodeAnnouncement { + + t.Helper() + + msg := &lnwire.NodeAnnouncement{ + Features: rawFeatureVector(), + Timestamp: uint32(r.Int31()), + Alias: randAlias(r), + RGBColor: color.RGBA{ + R: uint8(r.Intn(1 << 8)), + G: uint8(r.Intn(1 << 8)), + B: uint8(r.Intn(1 << 8)), + }, + NodeID: randRawKey(t), + Addresses: randAddrs(t, r), + ExtraOpaqueData: createExtraData(t, r), + Signature: testNodeSig, + } + + return msg +} + +func newMsgChannelUpdate(t testing.TB, r *rand.Rand) *lnwire.ChannelUpdate { + t.Helper() + + msgFlags := lnwire.ChanUpdateMsgFlags(r.Int31()) + maxHtlc := lnwire.MilliSatoshi(r.Int63()) + + // We make the max_htlc field zero if it is not flagged + // as being part of the ChannelUpdate, to pass + // serialization tests, as it will be ignored if the bit + // is not set. + if msgFlags&lnwire.ChanUpdateOptionMaxHtlc == 0 { + maxHtlc = 0 + } + + msg := &lnwire.ChannelUpdate{ + ShortChannelID: lnwire.NewShortChanIDFromInt(r.Uint64()), + Timestamp: uint32(r.Int31()), + MessageFlags: msgFlags, + ChannelFlags: lnwire.ChanUpdateChanFlags(r.Int31()), + TimeLockDelta: uint16(r.Int31()), + HtlcMinimumMsat: lnwire.MilliSatoshi(r.Int63()), + HtlcMaximumMsat: maxHtlc, + BaseFee: uint32(r.Int31()), + FeeRate: uint32(r.Int31()), + ExtraOpaqueData: createExtraData(t, r), + Signature: testNodeSig, + } + + _, err := r.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to generate chain hash") + + return msg +} + +func newMsgAnnounceSignatures(t testing.TB, + r *rand.Rand) *lnwire.AnnounceSignatures { + + t.Helper() + + msg := &lnwire.AnnounceSignatures{ + ShortChannelID: lnwire.NewShortChanIDFromInt( + uint64(r.Int63()), + ), + ExtraOpaqueData: createExtraData(t, r), + NodeSignature: testNodeSig, + BitcoinSignature: testNodeSig, + } + + _, err := r.Read(msg.ChannelID[:]) + require.NoError(t, err, "unable to generate chan id") + + return msg +} + +func newMsgQueryShortChanIDs(t testing.TB, + r *rand.Rand) *lnwire.QueryShortChanIDs { + + t.Helper() + + msg := &lnwire.QueryShortChanIDs{ + EncodingType: lnwire.EncodingSortedPlain, + ExtraData: createExtraData(t, r), + } + + _, err := rand.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + for i := 0; i < testNumChanIDs; i++ { + msg.ShortChanIDs = append(msg.ShortChanIDs, + lnwire.NewShortChanIDFromInt(uint64(r.Int63()))) + } + + return msg +} + +func newMsgQueryShortChanIDsZlib(t testing.TB, + r *rand.Rand) *lnwire.QueryShortChanIDs { + + t.Helper() + + msg := &lnwire.QueryShortChanIDs{ + EncodingType: lnwire.EncodingSortedZlib, + ExtraData: createExtraData(t, r), + } + + _, err := rand.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + for i := 0; i < testNumChanIDs; i++ { + msg.ShortChanIDs = append(msg.ShortChanIDs, + lnwire.NewShortChanIDFromInt(uint64(r.Int63()))) + } + + return msg +} + +func newMsgReplyShortChanIDsEnd(t testing.TB, + r *rand.Rand) *lnwire.ReplyShortChanIDsEnd { + + t.Helper() + + msg := lnwire.NewReplyShortChanIDsEnd() + + _, err := rand.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + msg.Complete = uint8(r.Int31n(2)) + msg.ExtraData = createExtraData(t, r) + + return msg +} + +func newMsgQueryChannelRange(t testing.TB, + r *rand.Rand) *lnwire.QueryChannelRange { + + t.Helper() + + msg := lnwire.NewQueryChannelRange() + + _, err := rand.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + msg.FirstBlockHeight = r.Uint32() + msg.NumBlocks = r.Uint32() + msg.ExtraData = createExtraData(t, r) + + return msg +} + +func newMsgReplyChannelRange(t testing.TB, + r *rand.Rand) *lnwire.ReplyChannelRange { + + t.Helper() + + msg := &lnwire.ReplyChannelRange{ + EncodingType: lnwire.EncodingSortedPlain, + ExtraData: createExtraData(t, r), + } + + _, err := rand.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + msg.Complete = uint8(r.Int31n(2)) + + for i := 0; i < testNumChanIDs; i++ { + msg.ShortChanIDs = append(msg.ShortChanIDs, + lnwire.NewShortChanIDFromInt(uint64(r.Int63()))) + } + + return msg +} + +func newMsgReplyChannelRangeZlib(t testing.TB, + r *rand.Rand) *lnwire.ReplyChannelRange { + + t.Helper() + + msg := &lnwire.ReplyChannelRange{ + EncodingType: lnwire.EncodingSortedZlib, + ExtraData: createExtraData(t, r), + } + + _, err := rand.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + msg.Complete = uint8(r.Int31n(2)) + + for i := 0; i < testNumChanIDs; i++ { + msg.ShortChanIDs = append(msg.ShortChanIDs, + lnwire.NewShortChanIDFromInt(uint64(r.Int63()))) + } + + return msg +} + +func newMsgGossipTimestampRange(t testing.TB, + r *rand.Rand) *lnwire.GossipTimestampRange { + + t.Helper() + + msg := lnwire.NewGossipTimestampRange() + msg.FirstTimestamp = r.Uint32() + msg.TimestampRange = r.Uint32() + msg.ExtraData = createExtraData(t, r) + + _, err := r.Read(msg.ChainHash[:]) + require.NoError(t, err, "unable to read chain hash") + + return msg +} + +func randRawKey(t testing.TB) [33]byte { + t.Helper() + + var n [33]byte + + priv, err := btcec.NewPrivateKey(btcec.S256()) + require.NoError(t, err, "failed to create privKey") + + copy(n[:], priv.PubKey().SerializeCompressed()) + + return n +} + +func randPubKey(t testing.TB) *btcec.PublicKey { + t.Helper() + + priv, err := btcec.NewPrivateKey(btcec.S256()) + require.NoError(t, err, "failed to create pubkey") + + return priv.PubKey() +} + +func rawFeatureVector() *lnwire.RawFeatureVector { + // Get a slice of known feature bits. + featureBits := make([]lnwire.FeatureBit, 0, len(lnwire.Features)) + for fb := range lnwire.Features { + featureBits = append(featureBits, fb) + } + + featureVec := lnwire.NewRawFeatureVector(featureBits...) + + return featureVec +} + +func randDeliveryAddress(t testing.TB, r *rand.Rand) lnwire.DeliveryAddress { + t.Helper() + + // Generate a max sized address. + size := r.Intn(deliveryAddressMaxSize) + 1 + da := lnwire.DeliveryAddress(make([]byte, size)) + + _, err := r.Read(da) + require.NoError(t, err, "unable to read address") + return da +} + +func randTCP4Addr(t testing.TB, r *rand.Rand) *net.TCPAddr { + t.Helper() + + var ip [4]byte + _, err := r.Read(ip[:]) + require.NoError(t, err, "unable to read ip") + + var port [2]byte + _, err = r.Read(port[:]) + require.NoError(t, err, "unable to read port") + + addrIP := net.IP(ip[:]) + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &net.TCPAddr{IP: addrIP, Port: addrPort} +} + +func randTCP6Addr(t testing.TB, r *rand.Rand) *net.TCPAddr { + t.Helper() + + var ip [16]byte + + _, err := r.Read(ip[:]) + require.NoError(t, err, "unable to read ip") + + var port [2]byte + _, err = r.Read(port[:]) + require.NoError(t, err, "unable to read port") + + addrIP := net.IP(ip[:]) + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &net.TCPAddr{IP: addrIP, Port: addrPort} +} + +func randV2OnionAddr(t testing.TB, r *rand.Rand) *tor.OnionAddr { + t.Helper() + + var serviceID [tor.V2DecodedLen]byte + _, err := r.Read(serviceID[:]) + require.NoError(t, err, "unable to read serviceID") + + var port [2]byte + _, err = r.Read(port[:]) + require.NoError(t, err, "unable to read port") + + onionService := tor.Base32Encoding.EncodeToString(serviceID[:]) + onionService += tor.OnionSuffix + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &tor.OnionAddr{OnionService: onionService, Port: addrPort} +} + +func randV3OnionAddr(t testing.TB, r *rand.Rand) *tor.OnionAddr { + t.Helper() + + var serviceID [tor.V3DecodedLen]byte + _, err := r.Read(serviceID[:]) + require.NoError(t, err, "unable to read serviceID") + + var port [2]byte + _, err = r.Read(port[:]) + require.NoError(t, err, "unable to read port") + + onionService := tor.Base32Encoding.EncodeToString(serviceID[:]) + onionService += tor.OnionSuffix + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &tor.OnionAddr{OnionService: onionService, Port: addrPort} +} + +func randAddrs(t testing.TB, r *rand.Rand) []net.Addr { + tcp4Addr := randTCP4Addr(t, r) + tcp6Addr := randTCP6Addr(t, r) + v2OnionAddr := randV2OnionAddr(t, r) + v3OnionAddr := randV3OnionAddr(t, r) + + return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr} +} + +func randAlias(r *rand.Rand) lnwire.NodeAlias { + var a lnwire.NodeAlias + for i := range a { + a[i] = letterBytes[r.Intn(len(letterBytes))] + } + + return a +} + +func createExtraData(t testing.TB, r io.Reader) []byte { + t.Helper() + + // Read random bytes. + extraData := make([]byte, testNumExtraBytes) + _, err := r.Read(extraData) + require.NoError(t, err, "unable to generate extra data") + + // Encode the data length. + binary.BigEndian.PutUint16(extraData[:2], uint16(len(extraData[2:]))) + + return extraData +} diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go index 540137aae..4f1620281 100644 --- a/lnwire/node_announcement.go +++ b/lnwire/node_announcement.go @@ -124,17 +124,37 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { // Encode serializes the target NodeAnnouncement into the passed io.Writer // observing the protocol version specified. // -func (a *NodeAnnouncement) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - a.Signature, - a.Features, - a.Timestamp, - a.NodeID, - a.RGBColor, - a.Alias, - a.Addresses, - a.ExtraOpaqueData, - ) +// This is part of the lnwire.Message interface. +func (a *NodeAnnouncement) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteSig(w, a.Signature); err != nil { + return err + } + + if err := WriteRawFeatureVector(w, a.Features); err != nil { + return err + } + + if err := WriteUint32(w, a.Timestamp); err != nil { + return err + } + + if err := WriteBytes(w, a.NodeID[:]); err != nil { + return err + } + + if err := WriteColorRGBA(w, a.RGBColor); err != nil { + return err + } + + if err := WriteNodeAlias(w, a.Alias); err != nil { + return err + } + + if err := WriteNetAddrs(w, a.Addresses); err != nil { + return err + } + + return WriteBytes(w, a.ExtraOpaqueData) } // MsgType returns the integer uniquely identifying this message type on the @@ -149,19 +169,36 @@ func (a *NodeAnnouncement) MsgType() MessageType { func (a *NodeAnnouncement) DataToSign() ([]byte, error) { // We should not include the signatures itself. - var w bytes.Buffer - err := WriteElements(&w, - a.Features, - a.Timestamp, - a.NodeID, - a.RGBColor, - a.Alias[:], - a.Addresses, - a.ExtraOpaqueData, - ) - if err != nil { + buffer := make([]byte, 0, MaxMsgBody) + buf := bytes.NewBuffer(buffer) + + if err := WriteRawFeatureVector(buf, a.Features); err != nil { return nil, err } - return w.Bytes(), nil + if err := WriteUint32(buf, a.Timestamp); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.NodeID[:]); err != nil { + return nil, err + } + + if err := WriteColorRGBA(buf, a.RGBColor); err != nil { + return nil, err + } + + if err := WriteNodeAlias(buf, a.Alias); err != nil { + return nil, err + } + + if err := WriteNetAddrs(buf, a.Addresses); err != nil { + return nil, err + } + + if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil { + return nil, err + } + + return buf.Bytes(), nil } diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 35555e266..60d61184d 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -419,8 +419,12 @@ func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailIncorrectDetails) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, f.amount, f.height) +func (f *FailIncorrectDetails) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteMilliSatoshi(w, f.amount); err != nil { + return err + } + + return WriteUint32(w, f.height) } // FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final @@ -485,8 +489,8 @@ func (f *FailInvalidOnionVersion) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionVersion) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.OnionSHA256[:]) +func (f *FailInvalidOnionVersion) Encode(w *bytes.Buffer, pver uint32) error { + return WriteBytes(w, f.OnionSHA256[:]) } // FailInvalidOnionHmac is return if the onion HMAC is incorrect. @@ -519,8 +523,8 @@ func (f *FailInvalidOnionHmac) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionHmac) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.OnionSHA256[:]) +func (f *FailInvalidOnionHmac) Encode(w *bytes.Buffer, pver uint32) error { + return WriteBytes(w, f.OnionSHA256[:]) } // Returns a human readable string describing the target FailureMessage. @@ -561,8 +565,8 @@ func (f *FailInvalidOnionKey) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionKey) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.OnionSHA256[:]) +func (f *FailInvalidOnionKey) Encode(w *bytes.Buffer, pver uint32) error { + return WriteBytes(w, f.OnionSHA256[:]) } // Returns a human readable string describing the target FailureMessage. @@ -670,7 +674,9 @@ func (f *FailTemporaryChannelFailure) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailTemporaryChannelFailure) Encode(w io.Writer, pver uint32) error { +func (f *FailTemporaryChannelFailure) Encode(w *bytes.Buffer, + pver uint32) error { + var payload []byte if f.Update != nil { var bw bytes.Buffer @@ -680,7 +686,7 @@ func (f *FailTemporaryChannelFailure) Encode(w io.Writer, pver uint32) error { payload = bw.Bytes() } - if err := WriteElement(w, uint16(len(payload))); err != nil { + if err := WriteUint16(w, uint16(len(payload))); err != nil { return err } @@ -749,8 +755,8 @@ func (f *FailAmountBelowMinimum) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailAmountBelowMinimum) Encode(w io.Writer, pver uint32) error { - if err := WriteElement(w, f.HtlcMsat); err != nil { +func (f *FailAmountBelowMinimum) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil { return err } @@ -817,8 +823,8 @@ func (f *FailFeeInsufficient) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailFeeInsufficient) Encode(w io.Writer, pver uint32) error { - if err := WriteElement(w, f.HtlcMsat); err != nil { +func (f *FailFeeInsufficient) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil { return err } @@ -885,8 +891,8 @@ func (f *FailIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailIncorrectCltvExpiry) Encode(w io.Writer, pver uint32) error { - if err := WriteElement(w, f.CltvExpiry); err != nil { +func (f *FailIncorrectCltvExpiry) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteUint32(w, f.CltvExpiry); err != nil { return err } @@ -942,7 +948,7 @@ func (f *FailExpiryTooSoon) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailExpiryTooSoon) Encode(w io.Writer, pver uint32) error { +func (f *FailExpiryTooSoon) Encode(w *bytes.Buffer, pver uint32) error { return writeOnionErrorChanUpdate(w, &f.Update, pver) } @@ -1006,8 +1012,8 @@ func (f *FailChannelDisabled) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailChannelDisabled) Encode(w io.Writer, pver uint32) error { - if err := WriteElement(w, f.Flags); err != nil { +func (f *FailChannelDisabled) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteUint16(w, f.Flags); err != nil { return err } @@ -1056,8 +1062,10 @@ func (f *FailFinalIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailFinalIncorrectCltvExpiry) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.CltvExpiry) +func (f *FailFinalIncorrectCltvExpiry) Encode(w *bytes.Buffer, + pver uint32) error { + + return WriteUint32(w, f.CltvExpiry) } // FailFinalIncorrectHtlcAmount is returned if the amt_to_forward is higher @@ -1102,8 +1110,10 @@ func (f *FailFinalIncorrectHtlcAmount) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *FailFinalIncorrectHtlcAmount) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.IncomingHTLCAmount) +func (f *FailFinalIncorrectHtlcAmount) Encode(w *bytes.Buffer, + pver uint32) error { + + return WriteMilliSatoshi(w, f.IncomingHTLCAmount) } // FailExpiryTooFar is returned if the CLTV expiry in the HTLC is too far in the @@ -1177,13 +1187,13 @@ func (f *InvalidOnionPayload) Decode(r io.Reader, pver uint32) error { // Encode writes the failure in bytes stream. // // NOTE: Part of the Serializable interface. -func (f *InvalidOnionPayload) Encode(w io.Writer, pver uint32) error { +func (f *InvalidOnionPayload) Encode(w *bytes.Buffer, pver uint32) error { var buf [8]byte if err := tlv.WriteVarInt(w, f.Type, &buf); err != nil { return err } - return WriteElements(w, f.Offset) + return WriteUint16(w, f.Offset) } // FailMPPTimeout is returned if the complete amount for a multi part payment @@ -1263,7 +1273,7 @@ func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) { // EncodeFailure encodes, including the necessary onion failure header // information. -func EncodeFailure(w io.Writer, failure FailureMessage, pver uint32) error { +func EncodeFailure(w *bytes.Buffer, failure FailureMessage, pver uint32) error { var failureMessageBuffer bytes.Buffer err := EncodeFailureMessage(&failureMessageBuffer, failure, pver) @@ -1283,17 +1293,25 @@ func EncodeFailure(w io.Writer, failure FailureMessage, pver uint32) error { // messages are fixed size. pad := make([]byte, FailureMessageLength-len(failureMessage)) - return WriteElements(w, - uint16(len(failureMessage)), - failureMessage, - uint16(len(pad)), - pad, - ) + if err := WriteUint16(w, uint16(len(failureMessage))); err != nil { + return err + } + + if err := WriteBytes(w, failureMessage); err != nil { + return err + } + if err := WriteUint16(w, uint16(len(pad))); err != nil { + return err + } + + return WriteBytes(w, pad) } // EncodeFailureMessage encodes just the failure message without adding a length // and padding the message for the onion protocol. -func EncodeFailureMessage(w io.Writer, failure FailureMessage, pver uint32) error { +func EncodeFailureMessage(w *bytes.Buffer, + failure FailureMessage, pver uint32) error { + // First, we'll write out the error code itself into the failure // buffer. var codeBytes [2]byte @@ -1401,7 +1419,7 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) { // writeOnionErrorChanUpdate writes out a ChannelUpdate using the onion error // format. The format is that we first write out the true serialized length of // the channel update, followed by the serialized channel update itself. -func writeOnionErrorChanUpdate(w io.Writer, chanUpdate *ChannelUpdate, +func writeOnionErrorChanUpdate(w *bytes.Buffer, chanUpdate *ChannelUpdate, pver uint32) error { // First, we encode the channel update in a temporary buffer in order @@ -1414,7 +1432,7 @@ func writeOnionErrorChanUpdate(w io.Writer, chanUpdate *ChannelUpdate, // Now that we know the size, we can write the length out in the main // writer. updateLen := b.Len() - if err := WriteElement(w, uint16(updateLen)); err != nil { + if err := WriteUint16(w, uint16(updateLen)); err != nil { return err } diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 8c4c131c6..16f9d4c36 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -275,6 +275,8 @@ func (f *mockFailIncorrectDetailsNoHeight) Decode(r io.Reader, pver uint32) erro return nil } -func (f *mockFailIncorrectDetailsNoHeight) Encode(w io.Writer, pver uint32) error { - return WriteElement(w, f.amount) +func (f *mockFailIncorrectDetailsNoHeight) Encode(w *bytes.Buffer, + pver uint32) error { + + return WriteUint64(w, f.amount) } diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 9407d0037..035297ba2 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/btcec" @@ -150,7 +151,7 @@ var _ Message = (*OpenChannel)(nil) // protocol version. // // This is part of the lnwire.Message interface. -func (o *OpenChannel) Encode(w io.Writer, pver uint32) error { +func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error { // Since the upfront script is encoded as a TLV record, concatenate it // with the ExtraData, and write them as one. tlvRecords, err := packShutdownScript( @@ -160,27 +161,80 @@ func (o *OpenChannel) Encode(w io.Writer, pver uint32) error { return err } - return WriteElements(w, - o.ChainHash[:], - o.PendingChannelID[:], - o.FundingAmount, - o.PushAmount, - o.DustLimit, - o.MaxValueInFlight, - o.ChannelReserve, - o.HtlcMinimum, - o.FeePerKiloWeight, - o.CsvDelay, - o.MaxAcceptedHTLCs, - o.FundingKey, - o.RevocationPoint, - o.PaymentPoint, - o.DelayedPaymentPoint, - o.HtlcPoint, - o.FirstCommitmentPoint, - o.ChannelFlags, - tlvRecords, - ) + if err := WriteBytes(w, o.ChainHash[:]); err != nil { + return err + } + + if err := WriteBytes(w, o.PendingChannelID[:]); err != nil { + return err + } + + if err := WriteSatoshi(w, o.FundingAmount); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, o.PushAmount); err != nil { + return err + } + + if err := WriteSatoshi(w, o.DustLimit); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, o.MaxValueInFlight); err != nil { + return err + } + + if err := WriteSatoshi(w, o.ChannelReserve); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, o.HtlcMinimum); err != nil { + return err + } + + if err := WriteUint32(w, o.FeePerKiloWeight); err != nil { + return err + } + + if err := WriteUint16(w, o.CsvDelay); err != nil { + return err + } + + if err := WriteUint16(w, o.MaxAcceptedHTLCs); err != nil { + return err + } + + if err := WritePublicKey(w, o.FundingKey); err != nil { + return err + } + + if err := WritePublicKey(w, o.RevocationPoint); err != nil { + return err + } + + if err := WritePublicKey(w, o.PaymentPoint); err != nil { + return err + } + + if err := WritePublicKey(w, o.DelayedPaymentPoint); err != nil { + return err + } + + if err := WritePublicKey(w, o.HtlcPoint); err != nil { + return err + + } + + if err := WritePublicKey(w, o.FirstCommitmentPoint); err != nil { + return err + } + + if err := WriteFundingFlag(w, o.ChannelFlags); err != nil { + return err + } + + return WriteBytes(w, tlvRecords) } // Decode deserializes the serialized OpenChannel stored in the passed diff --git a/lnwire/ping.go b/lnwire/ping.go index e7e160d44..1e2877d0c 100644 --- a/lnwire/ping.go +++ b/lnwire/ping.go @@ -1,6 +1,9 @@ package lnwire -import "io" +import ( + "bytes" + "io" +) // PingPayload is a set of opaque bytes used to pad out a ping message. type PingPayload []byte @@ -44,10 +47,12 @@ func (p *Ping) Decode(r io.Reader, pver uint32) error { // protocol version specified. // // This is part of the lnwire.Message interface. -func (p *Ping) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - p.NumPongBytes, - p.PaddingBytes) +func (p *Ping) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteUint16(w, p.NumPongBytes); err != nil { + return err + } + + return WritePingPayload(w, p.PaddingBytes) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/pong.go b/lnwire/pong.go index b0d54523b..953eef5f5 100644 --- a/lnwire/pong.go +++ b/lnwire/pong.go @@ -1,6 +1,9 @@ package lnwire -import "io" +import ( + "bytes" + "io" +) // PongPayload is a set of opaque bytes sent in response to a ping message. type PongPayload []byte @@ -40,10 +43,8 @@ func (p *Pong) Decode(r io.Reader, pver uint32) error { // protocol version specified. // // This is part of the lnwire.Message interface. -func (p *Pong) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - p.PongBytes, - ) +func (p *Pong) Encode(w *bytes.Buffer, pver uint32) error { + return WritePongPayload(w, p.PongBytes) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/query_channel_range.go b/lnwire/query_channel_range.go index 167a4c64e..cfe88e9bf 100644 --- a/lnwire/query_channel_range.go +++ b/lnwire/query_channel_range.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "math" @@ -58,13 +59,20 @@ func (q *QueryChannelRange) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (q *QueryChannelRange) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - q.ChainHash[:], - q.FirstBlockHeight, - q.NumBlocks, - q.ExtraData, - ) +func (q *QueryChannelRange) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteBytes(w, q.ChainHash[:]); err != nil { + return err + } + + if err := WriteUint32(w, q.FirstBlockHeight); err != nil { + return err + } + + if err := WriteUint32(w, q.NumBlocks); err != nil { + return err + } + + return WriteBytes(w, q.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/query_short_chan_ids.go b/lnwire/query_short_chan_ids.go index 92ffaf585..323a936db 100644 --- a/lnwire/query_short_chan_ids.go +++ b/lnwire/query_short_chan_ids.go @@ -291,26 +291,25 @@ func decodeShortChanIDs(r io.Reader) (ShortChanIDEncoding, []ShortChannelID, err // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (q *QueryShortChanIDs) Encode(w io.Writer, pver uint32) error { +func (q *QueryShortChanIDs) Encode(w *bytes.Buffer, pver uint32) error { // First, we'll write out the chain hash. - err := WriteElements(w, q.ChainHash[:]) - if err != nil { + if err := WriteBytes(w, q.ChainHash[:]); err != nil { return err } // Base on our encoding type, we'll write out the set of short channel // ID's. - err = encodeShortChanIDs(w, q.EncodingType, q.ShortChanIDs, q.noSort) + err := encodeShortChanIDs(w, q.EncodingType, q.ShortChanIDs, q.noSort) if err != nil { return err } - return q.ExtraData.Encode(w) + return WriteBytes(w, q.ExtraData) } // encodeShortChanIDs encodes the passed short channel ID's into the passed // io.Writer, respecting the specified encoding type. -func encodeShortChanIDs(w io.Writer, encodingType ShortChanIDEncoding, +func encodeShortChanIDs(w *bytes.Buffer, encodingType ShortChanIDEncoding, shortChanIDs []ShortChannelID, noSort bool) error { // For both of the current encoding types, the channel ID's are to be @@ -332,20 +331,21 @@ func encodeShortChanIDs(w io.Writer, encodingType ShortChanIDEncoding, // body. We add 1 as the response will have the encoding type // prepended to it. numBytesBody := uint16(len(shortChanIDs)*8) + 1 - if err := WriteElements(w, numBytesBody); err != nil { + if err := WriteUint16(w, numBytesBody); err != nil { return err } // We'll then write out the encoding that that follows the // actual encoded short channel ID's. - if err := WriteElements(w, encodingType); err != nil { + err := WriteShortChanIDEncoding(w, encodingType) + if err != nil { return err } // Now that we know they're sorted, we can write out each short // channel ID to the buffer. for _, chanID := range shortChanIDs { - if err := WriteElements(w, chanID); err != nil { + if err := WriteShortChannelID(w, chanID); err != nil { return fmt.Errorf("unable to write short chan "+ "ID: %v", err) } @@ -360,29 +360,40 @@ func encodeShortChanIDs(w io.Writer, encodingType ShortChanIDEncoding, // TODO(roasbeef): assumes the caller knows the proper chunk size to // pass to avoid bin-packing here case EncodingSortedZlib: - // We'll make a new buffer, then wrap that with a zlib writer - // so we can write directly to the buffer and encode in a - // streaming manner. - var buf bytes.Buffer - zlibWriter := zlib.NewWriter(&buf) - // If we don't have anything at all to write, then we'll write // an empty payload so we don't include things like the zlib // header when the remote party is expecting no actual short // channel IDs. var compressedPayload []byte if len(shortChanIDs) > 0 { + // We'll make a new write buffer to hold the bytes of + // shortChanIDs. + var wb bytes.Buffer + // Next, we'll write out all the channel ID's directly // into the zlib writer, which will do compressing on // the fly. for _, chanID := range shortChanIDs { - err := WriteElements(zlibWriter, chanID) + err := WriteShortChannelID(&wb, chanID) if err != nil { - return fmt.Errorf("unable to write short chan "+ - "ID: %v", err) + return fmt.Errorf( + "unable to write short chan "+ + "ID: %v", err, + ) } } + // With shortChanIDs written into wb, we'll create a + // zlib writer and write all the compressed bytes. + var zlibBuffer bytes.Buffer + zlibWriter := zlib.NewWriter(&zlibBuffer) + + if _, err := zlibWriter.Write(wb.Bytes()); err != nil { + return fmt.Errorf( + "unable to write compressed short chan"+ + "ID: %w", err) + } + // Now that we've written all the elements, we'll // ensure the compressed stream is written to the // underlying buffer. @@ -391,7 +402,7 @@ func encodeShortChanIDs(w io.Writer, encodingType ShortChanIDEncoding, "compression: %v", err) } - compressedPayload = buf.Bytes() + compressedPayload = zlibBuffer.Bytes() } // Now that we have all the items compressed, we can compute @@ -407,15 +418,15 @@ func encodeShortChanIDs(w io.Writer, encodingType ShortChanIDEncoding, // Finally, we can write out the number of bytes, the // compression type, and finally the buffer itself. - if err := WriteElements(w, uint16(numBytesBody)); err != nil { + if err := WriteUint16(w, uint16(numBytesBody)); err != nil { return err } - if err := WriteElements(w, encodingType); err != nil { + err := WriteShortChanIDEncoding(w, encodingType) + if err != nil { return err } - _, err := w.Write(compressedPayload) - return err + return WriteBytes(w, compressedPayload) default: // If we're trying to encode with an encoding type that we diff --git a/lnwire/reply_channel_range.go b/lnwire/reply_channel_range.go index 2e8085c65..9dc0fca9c 100644 --- a/lnwire/reply_channel_range.go +++ b/lnwire/reply_channel_range.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "math" @@ -85,23 +86,29 @@ func (c *ReplyChannelRange) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *ReplyChannelRange) Encode(w io.Writer, pver uint32) error { - err := WriteElements(w, - c.ChainHash[:], - c.FirstBlockHeight, - c.NumBlocks, - c.Complete, - ) +func (c *ReplyChannelRange) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteBytes(w, c.ChainHash[:]); err != nil { + return err + } + + if err := WriteUint32(w, c.FirstBlockHeight); err != nil { + return err + } + + if err := WriteUint32(w, c.NumBlocks); err != nil { + return err + } + + if err := WriteUint8(w, c.Complete); err != nil { + return err + } + + err := encodeShortChanIDs(w, c.EncodingType, c.ShortChanIDs, c.noSort) if err != nil { return err } - err = encodeShortChanIDs(w, c.EncodingType, c.ShortChanIDs, c.noSort) - if err != nil { - return err - } - - return c.ExtraData.Encode(w) + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/reply_short_chan_ids_end.go b/lnwire/reply_short_chan_ids_end.go index 2280dee4b..53676f719 100644 --- a/lnwire/reply_short_chan_ids_end.go +++ b/lnwire/reply_short_chan_ids_end.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -54,12 +55,16 @@ func (c *ReplyShortChanIDsEnd) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *ReplyShortChanIDsEnd) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChainHash[:], - c.Complete, - c.ExtraData, - ) +func (c *ReplyShortChanIDsEnd) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteBytes(w, c.ChainHash[:]); err != nil { + return err + } + + if err := WriteUint8(w, c.Complete); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go index 6562547fe..0acf3ef7b 100644 --- a/lnwire/revoke_and_ack.go +++ b/lnwire/revoke_and_ack.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" "github.com/btcsuite/btcd/btcec" @@ -65,13 +66,20 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *RevokeAndAck) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.Revocation[:], - c.NextRevocationKey, - c.ExtraData, - ) +func (c *RevokeAndAck) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteBytes(w, c.Revocation[:]); err != nil { + return err + } + + if err := WritePublicKey(w, c.NextRevocationKey); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index 4eec13523..2adb6a082 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -46,8 +47,16 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { // the protocol version specified. // // This is part of the lnwire.Message interface. -func (s *Shutdown) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, s.ChannelID, s.Address, s.ExtraData) +func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, s.ChannelID); err != nil { + return err + } + + if err := WriteDeliveryAddress(w, s.Address); err != nil { + return err + } + + return WriteBytes(w, s.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_add_htlc.go b/lnwire/update_add_htlc.go index 35fc5fc36..666a54942 100644 --- a/lnwire/update_add_htlc.go +++ b/lnwire/update_add_htlc.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -84,20 +85,36 @@ func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error { ) } -// Encode serializes the target UpdateAddHTLC into the passed io.Writer observing -// the protocol version specified. +// Encode serializes the target UpdateAddHTLC into the passed io.Writer +// observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *UpdateAddHTLC) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.ID, - c.Amount, - c.PaymentHash[:], - c.Expiry, - c.OnionBlob[:], - c.ExtraData, - ) +func (c *UpdateAddHTLC) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteUint64(w, c.ID); err != nil { + return err + } + + if err := WriteMilliSatoshi(w, c.Amount); err != nil { + return err + } + + if err := WriteBytes(w, c.PaymentHash[:]); err != nil { + return err + } + + if err := WriteUint32(w, c.Expiry); err != nil { + return err + } + + if err := WriteBytes(w, c.OnionBlob[:]); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_fail_htlc.go b/lnwire/update_fail_htlc.go index 8706c1dc8..61f02bac9 100644 --- a/lnwire/update_fail_htlc.go +++ b/lnwire/update_fail_htlc.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -54,13 +55,20 @@ func (c *UpdateFailHTLC) Decode(r io.Reader, pver uint32) error { // the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *UpdateFailHTLC) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.ID, - c.Reason, - c.ExtraData, - ) +func (c *UpdateFailHTLC) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteUint64(w, c.ID); err != nil { + return err + } + + if err := WriteOpaqueReason(w, c.Reason); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_fail_malformed_htlc.go b/lnwire/update_fail_malformed_htlc.go index e7994fccc..f28107a9a 100644 --- a/lnwire/update_fail_malformed_htlc.go +++ b/lnwire/update_fail_malformed_htlc.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "crypto/sha256" "io" ) @@ -53,14 +54,26 @@ func (c *UpdateFailMalformedHTLC) Decode(r io.Reader, pver uint32) error { // io.Writer observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *UpdateFailMalformedHTLC) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.ID, - c.ShaOnionBlob[:], - c.FailureCode, - c.ExtraData, - ) +func (c *UpdateFailMalformedHTLC) Encode(w *bytes.Buffer, + pver uint32) error { + + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteUint64(w, c.ID); err != nil { + return err + } + + if err := WriteBytes(w, c.ShaOnionBlob[:]); err != nil { + return err + } + + if err := WriteFailCode(w, c.FailureCode); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_fee.go b/lnwire/update_fee.go index 375b5c671..a7026044c 100644 --- a/lnwire/update_fee.go +++ b/lnwire/update_fee.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -51,12 +52,16 @@ func (c *UpdateFee) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *UpdateFee) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.FeePerKw, - c.ExtraData, - ) +func (c *UpdateFee) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteUint32(w, c.FeePerKw); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_fulfill_htlc.go b/lnwire/update_fulfill_htlc.go index 2b7f2e494..275a37c87 100644 --- a/lnwire/update_fulfill_htlc.go +++ b/lnwire/update_fulfill_htlc.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "io" ) @@ -60,13 +61,20 @@ func (c *UpdateFulfillHTLC) Decode(r io.Reader, pver uint32) error { // observing the protocol version specified. // // This is part of the lnwire.Message interface. -func (c *UpdateFulfillHTLC) Encode(w io.Writer, pver uint32) error { - return WriteElements(w, - c.ChanID, - c.ID, - c.PaymentPreimage[:], - c.ExtraData, - ) +func (c *UpdateFulfillHTLC) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteUint64(w, c.ID); err != nil { + return err + } + + if err := WriteBytes(w, c.PaymentPreimage[:]); err != nil { + return err + } + + return WriteBytes(w, c.ExtraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/writer.go b/lnwire/writer.go new file mode 100644 index 000000000..6a20c5715 --- /dev/null +++ b/lnwire/writer.go @@ -0,0 +1,431 @@ +package lnwire + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "image/color" + "math" + "net" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/tor" +) + +var ( + // ErrNilFeatureVector is returned when the supplied feature is nil. + ErrNilFeatureVector = errors.New("cannot write nil feature vector") + + // ErrPkScriptTooLong is returned when the length of the provided + // script exceeds 34. + ErrPkScriptTooLong = errors.New("'PkScript' too long") + + // ErrNilTCPAddress is returned when the supplied address is nil. + ErrNilTCPAddress = errors.New("cannot write nil TCPAddr") + + // ErrNilOnionAddress is returned when the supplied address is nil. + ErrNilOnionAddress = errors.New("cannot write nil onion address") + + // ErrNilNetAddress is returned when a nil value is used in []net.Addr. + ErrNilNetAddress = errors.New("cannot write nil address") + + // ErrNilPublicKey is returned when a nil pubkey is used. + ErrNilPublicKey = errors.New("cannot write nil pubkey") + + // ErrUnknownServiceLength is returned when the onion service length is + // unknown. + ErrUnknownServiceLength = errors.New("unknown onion service length") +) + +// ErrOutpointIndexTooBig is used when the outpoint index exceeds the max value +// of uint16. +func ErrOutpointIndexTooBig(index uint32) error { + return fmt.Errorf( + "index for outpoint (%v) is greater than "+ + "max index of %v", index, math.MaxUint16, + ) +} + +// WriteBytes appends the given bytes to the provided buffer. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func WriteBytes(buf *bytes.Buffer, b []byte) error { // nolint: interfacer + _, err := buf.Write(b) + return err +} + +// WriteUint8 appends the uint8 to the provided buffer. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func WriteUint8(buf *bytes.Buffer, n uint8) error { // nolint: interfacer + _, err := buf.Write([]byte{n}) + return err +} + +// WriteUint16 appends the uint16 to the provided buffer. It encodes the +// integer using big endian byte order. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func WriteUint16(buf *bytes.Buffer, n uint16) error { // nolint: interfacer + var b [2]byte + binary.BigEndian.PutUint16(b[:], n) + _, err := buf.Write(b[:]) + return err +} + +// WriteUint32 appends the uint32 to the provided buffer. It encodes the +// integer using big endian byte order. +func WriteUint32(buf *bytes.Buffer, n uint32) error { + var b [4]byte + binary.BigEndian.PutUint32(b[:], n) + _, err := buf.Write(b[:]) + return err +} + +// WriteUint64 appends the uint64 to the provided buffer. It encodes the +// integer using big endian byte order. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func WriteUint64(buf *bytes.Buffer, n uint64) error { // nolint: interfacer + var b [8]byte + binary.BigEndian.PutUint64(b[:], n) + _, err := buf.Write(b[:]) + return err +} + +// WriteSatoshi appends the Satoshi value to the provided buffer. +func WriteSatoshi(buf *bytes.Buffer, amount btcutil.Amount) error { + return WriteUint64(buf, uint64(amount)) +} + +// WriteMilliSatoshi appends the MilliSatoshi value to the provided buffer. +func WriteMilliSatoshi(buf *bytes.Buffer, amount MilliSatoshi) error { + return WriteUint64(buf, uint64(amount)) +} + +// WritePublicKey appends the compressed public key to the provided buffer. +func WritePublicKey(buf *bytes.Buffer, pub *btcec.PublicKey) error { + if pub == nil { + return ErrNilPublicKey + } + + serializedPubkey := pub.SerializeCompressed() + return WriteBytes(buf, serializedPubkey) + +} + +// WriteChannelID appends the ChannelID to the provided buffer. +func WriteChannelID(buf *bytes.Buffer, channelID ChannelID) error { + return WriteBytes(buf, channelID[:]) +} + +// WriteNodeAlias appends the alias to the provided buffer. +func WriteNodeAlias(buf *bytes.Buffer, alias NodeAlias) error { + return WriteBytes(buf, alias[:]) +} + +// WriteShortChannelID appends the ShortChannelID to the provided buffer. It +// encodes the BlockHeight and TxIndex each using 3 bytes with big endian byte +// order, and encodes txPosition using 2 bytes with big endian byte order. +func WriteShortChannelID(buf *bytes.Buffer, shortChanID ShortChannelID) error { + // Check that field fit in 3 bytes and write the blockHeight + if shortChanID.BlockHeight > ((1 << 24) - 1) { + return errors.New("block height should fit in 3 bytes") + } + + var blockHeight [4]byte + binary.BigEndian.PutUint32(blockHeight[:], shortChanID.BlockHeight) + + if _, err := buf.Write(blockHeight[1:]); err != nil { + return err + } + + // Check that field fit in 3 bytes and write the txIndex + if shortChanID.TxIndex > ((1 << 24) - 1) { + return errors.New("tx index should fit in 3 bytes") + } + + var txIndex [4]byte + binary.BigEndian.PutUint32(txIndex[:], shortChanID.TxIndex) + if _, err := buf.Write(txIndex[1:]); err != nil { + return err + } + + // Write the TxPosition + return WriteUint16(buf, shortChanID.TxPosition) +} + +// WriteSig appends the signature to the provided buffer. +func WriteSig(buf *bytes.Buffer, sig Sig) error { + return WriteBytes(buf, sig[:]) +} + +// WriteSigs appends the slice of signatures to the provided buffer with its +// length. +func WriteSigs(buf *bytes.Buffer, sigs []Sig) error { + // Write the length of the sigs. + if err := WriteUint16(buf, uint16(len(sigs))); err != nil { + return err + } + + for _, sig := range sigs { + if err := WriteSig(buf, sig); err != nil { + return err + } + } + return nil +} + +// WriteFailCode appends the FailCode to the provided buffer. +func WriteFailCode(buf *bytes.Buffer, e FailCode) error { + return WriteUint16(buf, uint16(e)) +} + +// WriteRawFeatureVector encodes the feature using the feature's Encode method +// and appends the data to the provided buffer. An error will return if the +// passed feature is nil. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func WriteRawFeatureVector(buf *bytes.Buffer, // nolint: interfacer + feature *RawFeatureVector) error { + + if feature == nil { + return ErrNilFeatureVector + } + + return feature.Encode(buf) +} + +// WriteColorRGBA appends the RGBA color using three bytes. +func WriteColorRGBA(buf *bytes.Buffer, e color.RGBA) error { + // Write R + if err := WriteUint8(buf, e.R); err != nil { + return err + } + + // Write G + if err := WriteUint8(buf, e.G); err != nil { + return err + } + + // Write B + return WriteUint8(buf, e.B) +} + +// WriteShortChanIDEncoding appends the ShortChanIDEncoding to the provided +// buffer. +func WriteShortChanIDEncoding(buf *bytes.Buffer, e ShortChanIDEncoding) error { + return WriteUint8(buf, uint8(e)) +} + +// WriteFundingFlag appends the FundingFlag to the provided buffer. +func WriteFundingFlag(buf *bytes.Buffer, flag FundingFlag) error { + return WriteUint8(buf, uint8(flag)) +} + +// WriteChanUpdateMsgFlags appends the update flag to the provided buffer. +func WriteChanUpdateMsgFlags(buf *bytes.Buffer, f ChanUpdateMsgFlags) error { + return WriteUint8(buf, uint8(f)) +} + +// WriteChanUpdateChanFlags appends the update flag to the provided buffer. +func WriteChanUpdateChanFlags(buf *bytes.Buffer, f ChanUpdateChanFlags) error { + return WriteUint8(buf, uint8(f)) +} + +// WriteDeliveryAddress appends the address to the provided buffer. +func WriteDeliveryAddress(buf *bytes.Buffer, addr DeliveryAddress) error { + return writeDataWithLength(buf, addr) +} + +// WritePingPayload appends the payload to the provided buffer. +func WritePingPayload(buf *bytes.Buffer, payload PingPayload) error { + return writeDataWithLength(buf, payload) +} + +// WritePongPayload appends the payload to the provided buffer. +func WritePongPayload(buf *bytes.Buffer, payload PongPayload) error { + return writeDataWithLength(buf, payload) +} + +// WriteErrorData appends the data to the provided buffer. +func WriteErrorData(buf *bytes.Buffer, data ErrorData) error { + return writeDataWithLength(buf, data) +} + +// WriteOpaqueReason appends the reason to the provided buffer. +func WriteOpaqueReason(buf *bytes.Buffer, reason OpaqueReason) error { + return writeDataWithLength(buf, reason) +} + +// WriteBool appends the boolean to the provided buffer. +func WriteBool(buf *bytes.Buffer, b bool) error { + if b { + return WriteBytes(buf, []byte{1}) + } + return WriteBytes(buf, []byte{0}) +} + +// WritePkScript appends the script to the provided buffer. Returns an error if +// the provided script exceeds 34 bytes. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func WritePkScript(buf *bytes.Buffer, s PkScript) error { // nolint: interfacer + // The largest script we'll accept is a p2wsh which is exactly + // 34 bytes long. + scriptLength := len(s) + if scriptLength > 34 { + return ErrPkScriptTooLong + } + + return wire.WriteVarBytes(buf, 0, s) +} + +// WriteOutPoint appends the outpoint to the provided buffer. +func WriteOutPoint(buf *bytes.Buffer, p wire.OutPoint) error { + // Before we write anything to the buffer, check the Index is sane. + if p.Index > math.MaxUint16 { + return ErrOutpointIndexTooBig(p.Index) + } + + var h [32]byte + copy(h[:], p.Hash[:]) + if _, err := buf.Write(h[:]); err != nil { + return err + } + + // Write the index using two bytes. + return WriteUint16(buf, uint16(p.Index)) +} + +// WriteTCPAddr appends the TCP address to the provided buffer, either a IPv4 +// or a IPv6. +func WriteTCPAddr(buf *bytes.Buffer, addr *net.TCPAddr) error { + if addr == nil { + return ErrNilTCPAddress + } + + // Make a slice of bytes to hold the data of descriptor and ip. At + // most, we need 17 bytes - 1 byte for the descriptor, 16 bytes for + // IPv6. + data := make([]byte, 0, 17) + + if addr.IP.To4() != nil { + data = append(data, uint8(tcp4Addr)) + data = append(data, addr.IP.To4()...) + } else { + data = append(data, uint8(tcp6Addr)) + data = append(data, addr.IP.To16()...) + } + + if _, err := buf.Write(data); err != nil { + return err + } + + return WriteUint16(buf, uint16(addr.Port)) +} + +// WriteOnionAddr appends the onion address to the provided buffer. +func WriteOnionAddr(buf *bytes.Buffer, addr *tor.OnionAddr) error { + if addr == nil { + return ErrNilOnionAddress + } + + var ( + suffixIndex int + descriptor []byte + ) + + // Decide the suffixIndex and descriptor. + switch len(addr.OnionService) { + case tor.V2Len: + descriptor = []byte{byte(v2OnionAddr)} + suffixIndex = tor.V2Len - tor.OnionSuffixLen + + case tor.V3Len: + descriptor = []byte{byte(v3OnionAddr)} + suffixIndex = tor.V3Len - tor.OnionSuffixLen + + default: + return ErrUnknownServiceLength + } + + // Decode the address. + host, err := tor.Base32Encoding.DecodeString( + addr.OnionService[:suffixIndex], + ) + if err != nil { + return err + } + + // Perform the actual write when the above checks passed. + if _, err := buf.Write(descriptor); err != nil { + return err + } + if _, err := buf.Write(host); err != nil { + return err + } + + return WriteUint16(buf, uint16(addr.Port)) +} + +// WriteNetAddrs appends a slice of addresses to the provided buffer with the +// length info. +func WriteNetAddrs(buf *bytes.Buffer, addresses []net.Addr) error { + // First, we'll encode all the addresses into an intermediate + // buffer. We need to do this in order to compute the total + // length of the addresses. + buffer := make([]byte, 0, MaxMsgBody) + addrBuf := bytes.NewBuffer(buffer) + + for _, address := range addresses { + switch a := address.(type) { + case *net.TCPAddr: + if err := WriteTCPAddr(addrBuf, a); err != nil { + return err + } + case *tor.OnionAddr: + if err := WriteOnionAddr(addrBuf, a); err != nil { + return err + } + default: + return ErrNilNetAddress + } + } + + // With the addresses fully encoded, we can now write out data. + return writeDataWithLength(buf, addrBuf.Bytes()) +} + +// writeDataWithLength writes the data and its length to the buffer. +// +// Note: We intentionally skip the interfacer linter check here because we want +// to have concrete type (bytes.Buffer) rather than interface type (io.Write) +// due to performance concern. +func writeDataWithLength(buf *bytes.Buffer, // nolint: interfacer + data []byte) error { + + var l [2]byte + binary.BigEndian.PutUint16(l[:], uint16(len(data))) + if _, err := buf.Write(l[:]); err != nil { + return err + } + + _, err := buf.Write(data) + return err +} diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go new file mode 100644 index 000000000..96ef66103 --- /dev/null +++ b/lnwire/writer_test.go @@ -0,0 +1,618 @@ +package lnwire + +import ( + "bytes" + "encoding/base32" + "image/color" + "math" + "net" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/tor" + "github.com/stretchr/testify/require" +) + +func TestWriteBytes(t *testing.T) { + buf := new(bytes.Buffer) + data := []byte{1, 2, 3} + + err := WriteBytes(buf, data) + + require.NoError(t, err) + require.Equal(t, data, buf.Bytes()) +} + +func TestWriteUint8(t *testing.T) { + buf := new(bytes.Buffer) + data := uint8(1) + expectedBytes := []byte{1} + + err := WriteUint8(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteUint16(t *testing.T) { + buf := new(bytes.Buffer) + data := uint16(1) + expectedBytes := []byte{0, 1} + + err := WriteUint16(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteUint32(t *testing.T) { + buf := new(bytes.Buffer) + data := uint32(1) + expectedBytes := []byte{0, 0, 0, 1} + + err := WriteUint32(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteUint64(t *testing.T) { + buf := new(bytes.Buffer) + data := uint64(1) + expectedBytes := []byte{0, 0, 0, 0, 0, 0, 0, 1} + + err := WriteUint64(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteSatoshi(t *testing.T) { + buf := new(bytes.Buffer) + data := btcutil.Amount(1) + expectedBytes := []byte{0, 0, 0, 0, 0, 0, 0, 1} + + err := WriteSatoshi(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteMilliSatoshi(t *testing.T) { + buf := new(bytes.Buffer) + data := MilliSatoshi(1) + expectedBytes := []byte{0, 0, 0, 0, 0, 0, 0, 1} + + err := WriteMilliSatoshi(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWritePublicKey(t *testing.T) { + buf := new(bytes.Buffer) + + // Check that when nil pubkey is used, an error will return. + err := WritePublicKey(buf, nil) + require.Equal(t, ErrNilPublicKey, err) + + pub, err := randPubKey() + require.NoError(t, err) + expectedBytes := pub.SerializeCompressed() + + err = WritePublicKey(buf, pub) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteChannelID(t *testing.T) { + buf := new(bytes.Buffer) + data := ChannelID{1} + expectedBytes := [32]byte{1} + + err := WriteChannelID(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes[:], buf.Bytes()) +} + +func TestWriteNodeAlias(t *testing.T) { + buf := new(bytes.Buffer) + data := NodeAlias{1} + expectedBytes := [32]byte{1} + + err := WriteNodeAlias(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes[:], buf.Bytes()) +} + +func TestWriteShortChannelID(t *testing.T) { + buf := new(bytes.Buffer) + data := ShortChannelID{BlockHeight: 1, TxIndex: 2, TxPosition: 3} + expectedBytes := []byte{ + 0, 0, 1, // First three bytes encodes BlockHeight. + 0, 0, 2, // Second three bytes encodes TxIndex. + 0, 3, // Final two bytes encodes TxPosition. + } + + err := WriteShortChannelID(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteSig(t *testing.T) { + buf := new(bytes.Buffer) + data := Sig{1, 2, 3} + expectedBytes := [64]byte{1, 2, 3} + + err := WriteSig(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes[:], buf.Bytes()) +} + +func TestWriteSigs(t *testing.T) { + buf := new(bytes.Buffer) + sig1, sig2, sig3 := Sig{1}, Sig{2}, Sig{3} + data := []Sig{sig1, sig2, sig3} + + // First two bytes encode the length of the slice. + expectedBytes := []byte{0, 3} + expectedBytes = append(expectedBytes, sig1[:]...) + expectedBytes = append(expectedBytes, sig2[:]...) + expectedBytes = append(expectedBytes, sig3[:]...) + + err := WriteSigs(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteFailCode(t *testing.T) { + buf := new(bytes.Buffer) + data := FailCode(1) + expectedBytes := []byte{0, 1} + + err := WriteFailCode(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +// TODO(yy): expand the test to cover more encoding scenarios. +func TestWriteRawFeatureVector(t *testing.T) { + buf := new(bytes.Buffer) + + // Check that when nil feature is used, an error will return. + err := WriteRawFeatureVector(buf, nil) + require.Equal(t, ErrNilFeatureVector, err) + + // Create a raw feature vector. + feature := &RawFeatureVector{features: map[FeatureBit]bool{ + InitialRoutingSync: true, // FeatureBit 3. + }} + expectedBytes := []byte{ + 0, 1, // First two bytes encode the length. + 8, // Last byte encodes the feature bit (1 << 3). + } + + err = WriteRawFeatureVector(buf, feature) + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteColorRGBA(t *testing.T) { + buf := new(bytes.Buffer) + data := color.RGBA{R: 1, G: 2, B: 3} + expectedBytes := []byte{1, 2, 3} + + err := WriteColorRGBA(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteShortChanIDEncoding(t *testing.T) { + buf := new(bytes.Buffer) + data := ShortChanIDEncoding(1) + expectedBytes := []byte{1} + + err := WriteShortChanIDEncoding(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteFundingFlag(t *testing.T) { + buf := new(bytes.Buffer) + data := FundingFlag(1) + expectedBytes := []byte{1} + + err := WriteFundingFlag(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteChanUpdateMsgFlags(t *testing.T) { + buf := new(bytes.Buffer) + data := ChanUpdateMsgFlags(1) + expectedBytes := []byte{1} + + err := WriteChanUpdateMsgFlags(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteChanUpdateChanFlags(t *testing.T) { + buf := new(bytes.Buffer) + data := ChanUpdateChanFlags(1) + expectedBytes := []byte{1} + + err := WriteChanUpdateChanFlags(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteDeliveryAddress(t *testing.T) { + buf := new(bytes.Buffer) + data := DeliveryAddress{1, 1, 1} + expectedBytes := []byte{ + 0, 3, // First two bytes encode the length. + 1, 1, 1, // The actual data. + } + + err := WriteDeliveryAddress(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWritePingPayload(t *testing.T) { + buf := new(bytes.Buffer) + data := PingPayload{1, 1, 1} + expectedBytes := []byte{ + 0, 3, // First two bytes encode the length. + 1, 1, 1, // The actual data. + } + + err := WritePingPayload(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWritePongPayload(t *testing.T) { + buf := new(bytes.Buffer) + data := PongPayload{1, 1, 1} + expectedBytes := []byte{ + 0, 3, // First two bytes encode the length. + 1, 1, 1, // The actual data. + } + + err := WritePongPayload(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteErrorData(t *testing.T) { + buf := new(bytes.Buffer) + data := ErrorData{1, 1, 1} + expectedBytes := []byte{ + 0, 3, // First two bytes encode the length. + 1, 1, 1, // The actual data. + } + + err := WriteErrorData(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteOpaqueReason(t *testing.T) { + buf := new(bytes.Buffer) + data := OpaqueReason{1, 1, 1} + expectedBytes := []byte{ + 0, 3, // First two bytes encode the length. + 1, 1, 1, // The actual data. + } + + err := WriteOpaqueReason(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteBool(t *testing.T) { + buf := new(bytes.Buffer) + + // Test write true. + data := true + expectedBytes := []byte{1} + + err := WriteBool(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) + + // Test write false. + data = false + expectedBytes = append(expectedBytes, 0) + + err = WriteBool(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWritePkScript(t *testing.T) { + buf := new(bytes.Buffer) + + // Write a very long script to check the error is returned as expected. + script := PkScript{} + zeros := [35]byte{} + script = append(script, zeros[:]...) + err := WritePkScript(buf, script) + require.Equal(t, ErrPkScriptTooLong, err) + + data := PkScript{1, 1, 1} + expectedBytes := []byte{ + 3, // First byte encodes the length. + 1, 1, 1, // The actual data. + } + + err = WritePkScript(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteOutPoint(t *testing.T) { + buf := new(bytes.Buffer) + + // Create an outpoint with very large index to check the error is + // returned as expected. + outpointWrong := wire.OutPoint{Index: math.MaxUint16 + 1} + err := WriteOutPoint(buf, outpointWrong) + require.Equal(t, ErrOutpointIndexTooBig(outpointWrong.Index), err) + + // Now check the normal write succeeds. + hash := chainhash.Hash{1} + data := wire.OutPoint{Index: 2, Hash: hash} + expectedBytes := []byte{} + expectedBytes = append(expectedBytes, hash[:]...) + expectedBytes = append(expectedBytes, []byte{0, 2}...) + + err = WriteOutPoint(buf, data) + + require.NoError(t, err) + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestWriteTCPAddr(t *testing.T) { + buf := new(bytes.Buffer) + + testCases := []struct { + name string + addr *net.TCPAddr + + expectedErr error + expectedBytes []byte + }{ + { + // Check that the error is returned when nil address is + // used. + name: "nil address err", + addr: nil, + expectedErr: ErrNilTCPAddress, + expectedBytes: nil, + }, + { + // Check write IPv4. + name: "write ipv4", + addr: &net.TCPAddr{ + IP: net.IP{127, 0, 0, 1}, + Port: 8080, + }, + expectedErr: nil, + expectedBytes: []byte{ + 0x1, // The addressType. + 0x7f, 0x0, 0x0, 0x1, // The IP. + 0x1f, 0x90, // The port (31 * 256 + 144). + }, + }, + { + // Check write IPv6. + name: "write ipv6", + addr: &net.TCPAddr{ + IP: net.IP{ + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + Port: 8080, + }, + expectedErr: nil, + expectedBytes: []byte{ + 0x2, // The addressType. + // The IP. + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1f, 0x90, // The port (31 * 256 + 144). + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + oldLen := buf.Len() + + err := WriteTCPAddr(buf, tc.addr) + require.Equal(t, tc.expectedErr, err) + + bytesWritten := buf.Bytes()[oldLen:buf.Len()] + require.Equal(t, tc.expectedBytes, bytesWritten) + }) + } +} + +func TestWriteOnionAddr(t *testing.T) { + buf := new(bytes.Buffer) + + testCases := []struct { + name string + addr *tor.OnionAddr + + expectedErr error + expectedBytes []byte + }{ + { + // Check that the error is returned when nil address is + // used. + name: "nil address err", + addr: nil, + expectedErr: ErrNilOnionAddress, + expectedBytes: nil, + }, + { + // Check the error is returned when an invalid onion + // address is used. + name: "wrong onion service length", + addr: &tor.OnionAddr{OnionService: "wrong"}, + expectedErr: ErrUnknownServiceLength, + expectedBytes: nil, + }, + { + // Check when the address has invalid base32 encoding, + // the error is returned. + name: "invalid base32 encoding", + addr: &tor.OnionAddr{ + OnionService: "1234567890123456.onion", + }, + expectedErr: base32.CorruptInputError(0), + expectedBytes: nil, + }, + { + // Check write onion v2. + name: "onion address v2", + addr: &tor.OnionAddr{ + OnionService: "abcdefghijklmnop.onion", + Port: 9065, + }, + expectedErr: nil, + expectedBytes: []byte{ + 0x3, // The descriptor. + 0x0, 0x44, 0x32, 0x14, 0xc7, // The host. + 0x42, 0x54, 0xb6, 0x35, 0xcf, + 0x23, 0x69, // The port. + }, + }, + { + // Check write onion v3. + name: "onion address v3", + addr: &tor.OnionAddr{ + OnionService: "abcdefghij" + + "abcdefghijabcdefghij" + + "abcdefghijabcdefghij" + + "234567.onion", + Port: 9065, + }, + expectedErr: nil, + expectedBytes: []byte{ + 0x4, // The descriptor. + 0x0, 0x44, 0x32, 0x14, 0xc7, 0x42, 0x40, + 0x11, 0xc, 0x85, 0x31, 0xd0, 0x90, 0x4, + 0x43, 0x21, 0x4c, 0x74, 0x24, 0x1, 0x10, + 0xc8, 0x53, 0x1d, 0x9, 0x0, 0x44, 0x32, + 0x14, 0xc7, 0x42, 0x75, 0xbe, 0x77, 0xdf, + 0x23, 0x69, // The port. + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + oldLen := buf.Len() + + err := WriteOnionAddr(buf, tc.addr) + require.Equal(t, tc.expectedErr, err) + + bytesWritten := buf.Bytes()[oldLen:buf.Len()] + require.Equal(t, tc.expectedBytes, bytesWritten) + }) + } +} + +func TestWriteNetAddrs(t *testing.T) { + buf := new(bytes.Buffer) + tcpAddr := &net.TCPAddr{ + IP: net.IP{127, 0, 0, 1}, + Port: 8080, + } + onionAddr := &tor.OnionAddr{ + OnionService: "abcdefghijklmnop.onion", + Port: 9065, + } + + testCases := []struct { + name string + addr []net.Addr + + expectedErr error + expectedBytes []byte + }{ + { + // Check that the error is returned when nil address is + // used. + name: "nil address err", + addr: []net.Addr{nil, tcpAddr, onionAddr}, + expectedErr: ErrNilNetAddress, + expectedBytes: nil, + }, + { + // Check empty address slice. + name: "empty address slice", + addr: []net.Addr{}, + expectedErr: nil, + // Use two bytes to encode the address size. + expectedBytes: []byte{0, 0}, + }, + { + // Check a successful writes of a slice of addresses. + name: "two addresses", + addr: []net.Addr{tcpAddr, onionAddr}, + expectedErr: nil, + expectedBytes: []byte{ + // 7 bytes for TCP and 13 bytes for onion. + 0x0, 0x14, + // TCP address. + 0x1, 0x7f, 0x0, 0x0, 0x1, 0x1f, 0x90, + // Onion address. + 0x3, 0x0, 0x44, 0x32, 0x14, 0xc7, 0x42, + 0x54, 0xb6, 0x35, 0xcf, 0x23, 0x69, + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + oldLen := buf.Len() + + err := WriteNetAddrs(buf, tc.addr) + require.Equal(t, tc.expectedErr, err) + + bytesWritten := buf.Bytes()[oldLen:buf.Len()] + require.Equal(t, tc.expectedBytes, bytesWritten) + }) + } +} diff --git a/watchtower/wtwire/message.go b/watchtower/wtwire/message.go index 2b3d22887..5bc8c75ce 100644 --- a/watchtower/wtwire/message.go +++ b/watchtower/wtwire/message.go @@ -5,8 +5,6 @@ import ( "encoding/binary" "fmt" "io" - - "github.com/lightningnetwork/lnd/lnwire" ) // MaxMessagePayload is the maximum bytes a message can be regardless of other @@ -75,7 +73,14 @@ func (m MessageType) String() string { // Serializable is an interface which defines a lightning wire serializable // object. -type Serializable = lnwire.Serializable +type Serializable interface { + // Decode reads the bytes stream and converts it to the object. + Decode(io.Reader, uint32) error + + // Encode converts object to the bytes stream and write it into the + // write buffer. + Encode(io.Writer, uint32) error +} // Message is an interface that defines a lightning wire protocol message. The // interface is general in order to allow implementing types full control over