From 941a123ab09c646328dff9594218078c3a1b5496 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 19:33:05 -0700 Subject: [PATCH 01/10] lnwire: add new ExtraOpaqueData field to gossip messages In this commit, we add a new field to all the existing gossip messages: ExtraOpqueData. We do this, as before this commit, if we came across a ChannelUpdate message with a set of optional fields, then we wouldn't be able to properly parse the signatures related to the message. If we never corrected this behavior, then we would violate the forwards compatible principle we use when parsing existing messages. As these messages can now be padded out to the max message size, we've increased the MaxPayloadLength value for all of these messages. Fixes #1814. --- lnwire/announcement_signatures.go | 49 ++++++++++++++-------- lnwire/channel_announcement.go | 67 ++++++++++++++----------------- lnwire/channel_update.go | 61 ++++++++++++++-------------- lnwire/node_announcement.go | 32 +++++++++++++-- lnwire/onion_error_test.go | 6 ++- 5 files changed, 125 insertions(+), 90 deletions(-) diff --git a/lnwire/announcement_signatures.go b/lnwire/announcement_signatures.go index 748f29b12..fd82211c3 100644 --- a/lnwire/announcement_signatures.go +++ b/lnwire/announcement_signatures.go @@ -1,6 +1,9 @@ package lnwire -import "io" +import ( + "io" + "io/ioutil" +) // AnnounceSignatures this is a direct message between two endpoints of a // channel and serves as an opt-in mechanism to allow the announcement of @@ -30,6 +33,14 @@ type AnnounceSignatures struct { // bitcoin key and and creating the reverse reference bitcoin_key -> // node_key. BitcoinSignature Sig + + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte } // A compile time check to ensure AnnounceSignatures implements the @@ -41,12 +52,29 @@ var _ Message = (*AnnounceSignatures)(nil) // // This is part of the lnwire.Message interface. func (a *AnnounceSignatures) Decode(r io.Reader, pver uint32) error { - return readElements(r, + err := readElements(r, &a.ChannelID, &a.ShortChannelID, &a.NodeSignature, &a.BitcoinSignature, ) + if err != nil { + return err + } + + // Now that we've read out all the fields that we explicitly know of, + // we'll collect the remainder into the ExtraOpaqueData field. If there + // aren't any bytes, then we'll snip off the slice to avoid carrying + // around excess capacity. + a.ExtraOpaqueData, err = ioutil.ReadAll(r) + if err != nil { + return err + } + if len(a.ExtraOpaqueData) == 0 { + a.ExtraOpaqueData = nil + } + + return nil } // Encode serializes the target AnnounceSignatures into the passed io.Writer @@ -59,6 +87,7 @@ func (a *AnnounceSignatures) Encode(w io.Writer, pver uint32) error { a.ShortChannelID, a.NodeSignature, a.BitcoinSignature, + a.ExtraOpaqueData, ) } @@ -75,19 +104,5 @@ func (a *AnnounceSignatures) MsgType() MessageType { // // This is part of the lnwire.Message interface. func (a *AnnounceSignatures) MaxPayloadLength(pver uint32) uint32 { - var length uint32 - - // ChannelID - 36 bytes - length += 36 - - // ShortChannelID - 8 bytes - length += 8 - - // NodeSignatures - 64 bytes - length += 64 - - // BitcoinSignatures - 64 bytes - length += 64 - - return length + return 65533 } diff --git a/lnwire/channel_announcement.go b/lnwire/channel_announcement.go index f9b86510b..70f532419 100644 --- a/lnwire/channel_announcement.go +++ b/lnwire/channel_announcement.go @@ -3,6 +3,7 @@ package lnwire import ( "bytes" "io" + "io/ioutil" "github.com/btcsuite/btcd/chaincfg/chainhash" ) @@ -48,6 +49,14 @@ type ChannelAnnouncement struct { // multisig funding transaction output. BitcoinKey1 [33]byte BitcoinKey2 [33]byte + + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte } // A compile time check to ensure ChannelAnnouncement implements the @@ -59,7 +68,7 @@ var _ Message = (*ChannelAnnouncement)(nil) // // This is part of the lnwire.Message interface. func (a *ChannelAnnouncement) Decode(r io.Reader, pver uint32) error { - return readElements(r, + err := readElements(r, &a.NodeSig1, &a.NodeSig2, &a.BitcoinSig1, @@ -72,6 +81,23 @@ func (a *ChannelAnnouncement) Decode(r io.Reader, pver uint32) error { &a.BitcoinKey1, &a.BitcoinKey2, ) + if err != nil { + return err + } + + // Now that we've read out all the fields that we explicitly know of, + // we'll collect the remainder into the ExtraOpaqueData field. If there + // aren't any bytes, then we'll snip off the slice to avoid carrying + // around excess capacity. + a.ExtraOpaqueData, err = ioutil.ReadAll(r) + if err != nil { + return err + } + if len(a.ExtraOpaqueData) == 0 { + a.ExtraOpaqueData = nil + } + + return nil } // Encode serializes the target ChannelAnnouncement into the passed io.Writer @@ -91,6 +117,7 @@ func (a *ChannelAnnouncement) Encode(w io.Writer, pver uint32) error { a.NodeID2, a.BitcoinKey1, a.BitcoinKey2, + a.ExtraOpaqueData, ) } @@ -107,42 +134,7 @@ func (a *ChannelAnnouncement) MsgType() MessageType { // // This is part of the lnwire.Message interface. func (a *ChannelAnnouncement) MaxPayloadLength(pver uint32) uint32 { - var length uint32 - - // NodeSig1 - 64 bytes - length += 64 - - // NodeSig2 - 64 bytes - length += 64 - - // BitcoinSig1 - 64 bytes - length += 64 - - // BitcoinSig2 - 64 bytes - length += 64 - - // Features (max possible features) - length += 65096 - - // ChainHash - 32 bytes - length += 32 - - // ShortChannelID - 8 bytes - length += 8 - - // NodeID1 - 33 bytes - length += 33 - - // NodeID2 - 33 bytes - length += 33 - - // BitcoinKey1 - 33 bytes - length += 33 - - // BitcoinKey2 - 33 bytes - length += 33 - - return length + return 65533 } // DataToSign is used to retrieve part of the announcement message which should @@ -158,6 +150,7 @@ func (a *ChannelAnnouncement) DataToSign() ([]byte, error) { a.NodeID2, a.BitcoinKey1, a.BitcoinKey2, + a.ExtraOpaqueData, ) if err != nil { return nil, err diff --git a/lnwire/channel_update.go b/lnwire/channel_update.go index 971f7bb3d..5cc3b4308 100644 --- a/lnwire/channel_update.go +++ b/lnwire/channel_update.go @@ -3,6 +3,7 @@ package lnwire import ( "bytes" "io" + "io/ioutil" "github.com/btcsuite/btcd/chaincfg/chainhash" ) @@ -73,6 +74,14 @@ type ChannelUpdate struct { // FeeRate is the fee rate that will be charged per millionth of a // satoshi. FeeRate uint32 + + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte } // A compile time check to ensure ChannelUpdate implements the lnwire.Message @@ -84,7 +93,7 @@ var _ Message = (*ChannelUpdate)(nil) // // This is part of the lnwire.Message interface. func (a *ChannelUpdate) Decode(r io.Reader, pver uint32) error { - return readElements(r, + err := readElements(r, &a.Signature, a.ChainHash[:], &a.ShortChannelID, @@ -95,6 +104,23 @@ func (a *ChannelUpdate) Decode(r io.Reader, pver uint32) error { &a.BaseFee, &a.FeeRate, ) + if err != nil { + return err + } + + // Now that we've read out all the fields that we explicitly know of, + // we'll collect the remainder into the ExtraOpaqueData field. If there + // aren't any bytes, then we'll snip off the slice to avoid carrying + // around excess capacity. + a.ExtraOpaqueData, err = ioutil.ReadAll(r) + if err != nil { + return err + } + if len(a.ExtraOpaqueData) == 0 { + a.ExtraOpaqueData = nil + } + + return nil } // Encode serializes the target ChannelUpdate into the passed io.Writer @@ -112,6 +138,7 @@ func (a *ChannelUpdate) Encode(w io.Writer, pver uint32) error { a.HtlcMinimumMsat, a.BaseFee, a.FeeRate, + a.ExtraOpaqueData, ) } @@ -128,36 +155,7 @@ func (a *ChannelUpdate) MsgType() MessageType { // // This is part of the lnwire.Message interface. func (a *ChannelUpdate) MaxPayloadLength(pver uint32) uint32 { - var length uint32 - - // Signature - 64 bytes - length += 64 - - // ChainHash - 64 bytes - length += 32 - - // ShortChannelID - 8 bytes - length += 8 - - // Timestamp - 4 bytes - length += 4 - - // Flags - 2 bytes - length += 2 - - // Expiry - 2 bytes - length += 2 - - // HtlcMinimumMstat - 8 bytes - length += 8 - - // FeeBaseMstat - 4 bytes - length += 4 - - // FeeProportionalMillionths - 4 bytes - length += 4 - - return length + return 65533 } // DataToSign is used to retrieve part of the announcement message which should @@ -175,6 +173,7 @@ func (a *ChannelUpdate) DataToSign() ([]byte, error) { a.HtlcMinimumMsat, a.BaseFee, a.FeeRate, + a.ExtraOpaqueData, ) if err != nil { return nil, err diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go index 3a0b7ff9b..f0ed70ea6 100644 --- a/lnwire/node_announcement.go +++ b/lnwire/node_announcement.go @@ -5,6 +5,7 @@ import ( "fmt" "image/color" "io" + "io/ioutil" "net" "unicode/utf8" ) @@ -83,6 +84,14 @@ type NodeAnnouncement struct { // Address includes two specification fields: 'ipv6' and 'port' on // which the node is accepting incoming connections. Addresses []net.Addr + + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte } // UpdateNodeAnnAddrs is a functional option that allows updating the addresses @@ -102,7 +111,7 @@ var _ Message = (*NodeAnnouncement)(nil) // // This is part of the lnwire.Message interface. func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { - return readElements(r, + err := readElements(r, &a.Signature, &a.Features, &a.Timestamp, @@ -111,6 +120,23 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { a.Alias[:], &a.Addresses, ) + if err != nil { + return err + } + + // Now that we've read out all the fields that we explicitly know of, + // we'll collect the remainder into the ExtraOpaqueData field. If there + // aren't any bytes, then we'll snip off the slice to avoid carrying + // around excess capacity. + a.ExtraOpaqueData, err = ioutil.ReadAll(r) + if err != nil { + return err + } + if len(a.ExtraOpaqueData) == 0 { + a.ExtraOpaqueData = nil + } + + return nil } // Encode serializes the target NodeAnnouncement into the passed io.Writer @@ -125,6 +151,7 @@ func (a *NodeAnnouncement) Encode(w io.Writer, pver uint32) error { a.RGBColor, a.Alias[:], a.Addresses, + a.ExtraOpaqueData, ) } @@ -156,12 +183,11 @@ func (a *NodeAnnouncement) DataToSign() ([]byte, error) { a.RGBColor, a.Alias[:], a.Addresses, + a.ExtraOpaqueData, ) if err != nil { return nil, err } - // TODO(roasbeef): also capture the excess bytes in msg padded out? - return w.Bytes(), nil } diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 9b2ab1019..d59281a28 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -6,6 +6,8 @@ import ( "encoding/binary" "reflect" "testing" + + "github.com/davecgh/go-spew/spew" ) var ( @@ -66,8 +68,8 @@ func TestEncodeDecodeCode(t *testing.T) { } if !reflect.DeepEqual(failure1, failure2) { - t.Fatalf("failure message are different, failure "+ - "code(%v)", failure1.Code()) + t.Fatalf("expected %v, got %v", spew.Sdump(failure1), + spew.Sdump(failure2)) } } } From adde6037c188e99e8a8395c4320544c40b8e78a6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 19:33:52 -0700 Subject: [PATCH 02/10] lnwire: extend quickcheck parser tests to add extra data for gossip messages --- lnwire/lnwire_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index d67fc9e7e..b30184bc3 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -537,6 +537,17 @@ func TestLightningWireProtocol(t *testing.T) { return } + numExtraBytes := r.Int31n(1000) + if numExtraBytes > 0 { + req.ExtraOpaqueData = make([]byte, numExtraBytes) + _, err := r.Read(req.ExtraOpaqueData[:]) + if err != nil { + t.Fatalf("unable to generate opaque "+ + "bytes: %v", err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgNodeAnnouncement: func(v []reflect.Value, r *rand.Rand) { @@ -574,6 +585,17 @@ func TestLightningWireProtocol(t *testing.T) { t.Fatalf("unable to generate addresses: %v", err) } + numExtraBytes := r.Int31n(1000) + if numExtraBytes > 0 { + req.ExtraOpaqueData = make([]byte, numExtraBytes) + _, err := r.Read(req.ExtraOpaqueData[:]) + if err != nil { + t.Fatalf("unable to generate opaque "+ + "bytes: %v", err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgChannelUpdate: func(v []reflect.Value, r *rand.Rand) { @@ -598,6 +620,17 @@ func TestLightningWireProtocol(t *testing.T) { return } + numExtraBytes := r.Int31n(1000) + if numExtraBytes > 0 { + req.ExtraOpaqueData = make([]byte, numExtraBytes) + _, err := r.Read(req.ExtraOpaqueData[:]) + if err != nil { + t.Fatalf("unable to generate opaque "+ + "bytes: %v", err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgAnnounceSignatures: func(v []reflect.Value, r *rand.Rand) { @@ -623,6 +656,17 @@ func TestLightningWireProtocol(t *testing.T) { return } + numExtraBytes := r.Int31n(1000) + if numExtraBytes > 0 { + req.ExtraOpaqueData = make([]byte, numExtraBytes) + _, err := r.Read(req.ExtraOpaqueData[:]) + if err != nil { + t.Fatalf("unable to generate opaque "+ + "bytes: %v", err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgChannelReestablish: func(v []reflect.Value, r *rand.Rand) { From f5c582d8d7b6fdaca2d6114720cff4cfb55ca4ec Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 19:36:16 -0700 Subject: [PATCH 03/10] channeldb: add new ExtraOpaqueData to edge policy+update and node ann In this commit, we add a mirror set of fields to the ones we recently added to the set of gossip wire messages. With these set of fields in place, we ensure that we'll be able to properly store and re-validate gossip messages that contain a set of extra/optional fields. --- channeldb/graph.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/channeldb/graph.go b/channeldb/graph.go index 1d7625ba7..340cc24df 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -1694,6 +1694,14 @@ type LightningNode struct { // Features is the list of protocol features supported by this node. Features *lnwire.FeatureVector + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte + db *DB // TODO(roasbeef): discovery will need storage to keep it's last IP @@ -1978,6 +1986,14 @@ type ChannelEdgeInfo struct { // the value output in the outpoint that created this channel. Capacity btcutil.Amount + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte + db *DB } @@ -2322,6 +2338,14 @@ type ChannelEdgePolicy struct { // this pointer the channel graph can further be traversed. Node *LightningNode + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte + db *DB } @@ -2691,6 +2715,11 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket, return err } + err = wire.WriteVarBytes(&b, 0, node.ExtraOpaqueData) + if err != nil { + return err + } + if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil { return err } @@ -2815,6 +2844,13 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) { return LightningNode{}, err } + // We'll try and see if there are any opaque bytes left, if not, then + // we'll ignore the EOF error and return the node as is. + node.ExtraOpaqueData, err = wire.ReadVarBytes(r, 0, 1000, "blob") + if err != nil && err != io.ErrUnexpectedEOF { + return LightningNode{}, err + } + return node, nil } @@ -2873,6 +2909,11 @@ func putChanEdgeInfo(edgeIndex *bolt.Bucket, edgeInfo *ChannelEdgeInfo, chanID [ return err } + err := wire.WriteVarBytes(&b, 0, edgeInfo.ExtraOpaqueData) + if err != nil { + return err + } + return edgeIndex.Put(chanID[:], b.Bytes()) } @@ -2950,6 +2991,13 @@ func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) { return ChannelEdgeInfo{}, err } + // We'll try and see if there are any opaque bytes left, if not, then + // we'll ignore the EOF error and return the edge as is. + edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes(r, 0, 1000, "blob") + if err != nil && err != io.ErrUnexpectedEOF { + return ChannelEdgeInfo{}, err + } + return edgeInfo, nil } @@ -2998,6 +3046,10 @@ func putChanEdgePolicy(edges, nodes *bolt.Bucket, edge *ChannelEdgePolicy, return err } + if err := wire.WriteVarBytes(&b, 0, edge.ExtraOpaqueData); err != nil { + return err + } + // Before we write out the new edge, we'll create a new entry in the // update index in order to keep it fresh. var indexKey [8 + 8]byte @@ -3182,6 +3234,13 @@ func deserializeChanEdgePolicy(r io.Reader, pub[:], err) } + // We'll try and see if there are any opaque bytes left, if not, then + // we'll ignore the EOF error and return the edge as is. + edge.ExtraOpaqueData, err = wire.ReadVarBytes(r, 0, 1000, "blob") + if err != nil && err != io.ErrUnexpectedEOF { + return nil, err + } + edge.Node = &node return edge, nil } From a35bdd423373ddb20dda0121e08191619d497a5a Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 20:12:45 -0700 Subject: [PATCH 04/10] channeldb: extend set of graph tests to include opque data where pertinent --- channeldb/graph_test.go | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index 4e1d10f71..abcde9ef7 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -87,6 +87,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) { Alias: "kek", Features: testFeatures, Addresses: testAddrs, + ExtraOpaqueData: []byte("extra new data"), db: db, } copy(node.PubKeyBytes[:], testPub.SerializeCompressed()) @@ -614,6 +615,11 @@ func assertEdgeInfoEqual(t *testing.T, e1 *ChannelEdgeInfo, t.Fatalf("capacity doesn't match: %v vs %v", e1.Capacity, e2.Capacity) } + + if !bytes.Equal(e1.ExtraOpaqueData, e2.ExtraOpaqueData) { + t.Fatalf("extra data doesn't match: %v vs %v", + e2.ExtraOpaqueData, e2.ExtraOpaqueData) + } } func TestEdgeInfoUpdates(t *testing.T) { @@ -675,8 +681,9 @@ func TestEdgeInfoUpdates(t *testing.T) { BitcoinSig1Bytes: testSig.Serialize(), BitcoinSig2Bytes: testSig.Serialize(), }, - ChannelPoint: outpoint, - Capacity: 1000, + ChannelPoint: outpoint, + Capacity: 1000, + ExtraOpaqueData: []byte("new unknown feature"), } copy(edgeInfo.NodeKey1Bytes[:], firstNode.PubKeyBytes[:]) copy(edgeInfo.NodeKey2Bytes[:], secondNode.PubKeyBytes[:]) @@ -697,8 +704,9 @@ func TestEdgeInfoUpdates(t *testing.T) { MinHTLC: 2342135, FeeBaseMSat: 4352345, FeeProportionalMillionths: 3452352, - Node: secondNode, - db: db, + Node: secondNode, + ExtraOpaqueData: []byte("new unknown feature2"), + db: db, } edge2 := &ChannelEdgePolicy{ SigBytes: testSig.Serialize(), @@ -709,8 +717,9 @@ func TestEdgeInfoUpdates(t *testing.T) { MinHTLC: 2342135, FeeBaseMSat: 4352345, FeeProportionalMillionths: 90392423, - Node: firstNode, - db: db, + Node: firstNode, + ExtraOpaqueData: []byte("new unknown feature1"), + db: db, } // Next, insert both nodes into the database, they should both be @@ -2460,6 +2469,10 @@ func compareNodes(a, b *LightningNode) error { return fmt.Errorf("HaveNodeAnnouncement doesn't match: expected %#v, \n "+ "got %#v", a.HaveNodeAnnouncement, b.HaveNodeAnnouncement) } + if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) { + return fmt.Errorf("extra data doesn't match: %v vs %v", + a.ExtraOpaqueData, b.ExtraOpaqueData) + } return nil } @@ -2496,6 +2509,10 @@ func compareEdgePolicies(a, b *ChannelEdgePolicy) error { "expected %v, got %v", a.FeeProportionalMillionths, b.FeeProportionalMillionths) } + if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) { + return fmt.Errorf("extra data doesn't match: %v vs %v", + a.ExtraOpaqueData, b.ExtraOpaqueData) + } if err := compareNodes(a.Node, b.Node); err != nil { return err } From 0fc20cee648bef92b3d796cc5be733c31da7b7b6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 20:13:14 -0700 Subject: [PATCH 05/10] discovery: update utils to properly include opaque data in gossip msgs --- discovery/utils.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/discovery/utils.go b/discovery/utils.go index 0cbc94708..63143c86c 100644 --- a/discovery/utils.go +++ b/discovery/utils.go @@ -23,13 +23,14 @@ func CreateChanAnnouncement(chanProof *channeldb.ChannelAuthProof, // authenticated channel announcement. chanID := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID) chanAnn := &lnwire.ChannelAnnouncement{ - ShortChannelID: chanID, - NodeID1: chanInfo.NodeKey1Bytes, - NodeID2: chanInfo.NodeKey2Bytes, - ChainHash: chanInfo.ChainHash, - BitcoinKey1: chanInfo.BitcoinKey1Bytes, - BitcoinKey2: chanInfo.BitcoinKey2Bytes, - Features: lnwire.NewRawFeatureVector(), + ShortChannelID: chanID, + NodeID1: chanInfo.NodeKey1Bytes, + NodeID2: chanInfo.NodeKey2Bytes, + ChainHash: chanInfo.ChainHash, + BitcoinKey1: chanInfo.BitcoinKey1Bytes, + BitcoinKey2: chanInfo.BitcoinKey2Bytes, + Features: lnwire.NewRawFeatureVector(), + ExtraOpaqueData: chanInfo.ExtraOpaqueData, } var err error @@ -76,6 +77,7 @@ func CreateChanAnnouncement(chanProof *channeldb.ChannelAuthProof, HtlcMinimumMsat: e1.MinHTLC, BaseFee: uint32(e1.FeeBaseMSat), FeeRate: uint32(e1.FeeProportionalMillionths), + ExtraOpaqueData: e1.ExtraOpaqueData, } edge1Ann.Signature, err = lnwire.NewSigFromRawSignature(e1.SigBytes) if err != nil { @@ -92,6 +94,7 @@ func CreateChanAnnouncement(chanProof *channeldb.ChannelAuthProof, HtlcMinimumMsat: e2.MinHTLC, BaseFee: uint32(e2.FeeBaseMSat), FeeRate: uint32(e2.FeeProportionalMillionths), + ExtraOpaqueData: e2.ExtraOpaqueData, } edge2Ann.Signature, err = lnwire.NewSigFromRawSignature(e2.SigBytes) if err != nil { From 841e24399c5e4b211c10f0282c7a1bff3ad8372e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 20:13:54 -0700 Subject: [PATCH 06/10] discovery: ensure we populate ExtraOpaqueData when writing/reading from disk --- discovery/gossiper.go | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 5f3b89273..dc4d2b0ee 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -275,13 +275,14 @@ func (d *AuthenticatedGossiper) SynchronizeNode(syncPeer lnpeer.Peer) error { return nil, err } return &lnwire.NodeAnnouncement{ - Signature: wireSig, - Timestamp: uint32(n.LastUpdate.Unix()), - Addresses: n.Addresses, - NodeID: n.PubKeyBytes, - Features: n.Features.RawFeatureVector, - RGBColor: n.Color, - Alias: alias, + Signature: wireSig, + Timestamp: uint32(n.LastUpdate.Unix()), + Addresses: n.Addresses, + NodeID: n.PubKeyBytes, + Features: n.Features.RawFeatureVector, + RGBColor: n.Color, + Alias: alias, + ExtraOpaqueData: n.ExtraOpaqueData, }, nil } @@ -1645,6 +1646,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement( AuthSigBytes: msg.Signature.ToSignatureBytes(), Features: features, Color: msg.RGBColor, + ExtraOpaqueData: msg.ExtraOpaqueData, } if err := d.cfg.Router.AddNode(node); err != nil { @@ -1767,6 +1769,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement( BitcoinKey2Bytes: msg.BitcoinKey2, AuthProof: proof, Features: featureBuf.Bytes(), + ExtraOpaqueData: msg.ExtraOpaqueData, } // We will add the edge to the channel router. If the nodes @@ -2036,6 +2039,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement( MinHTLC: msg.HtlcMinimumMsat, FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), + ExtraOpaqueData: msg.ExtraOpaqueData, } if err := d.cfg.Router.UpdateEdge(update); err != nil { @@ -2504,6 +2508,7 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, HtlcMinimumMsat: edge.MinHTLC, BaseFee: uint32(edge.FeeBaseMSat), FeeRate: uint32(edge.FeeProportionalMillionths), + ExtraOpaqueData: edge.ExtraOpaqueData, } chanUpdate.Signature, err = lnwire.NewSigFromRawSignature(edge.SigBytes) if err != nil { @@ -2545,13 +2550,14 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, if info.AuthProof != nil { chanID := lnwire.NewShortChanIDFromInt(info.ChannelID) chanAnn = &lnwire.ChannelAnnouncement{ - ShortChannelID: chanID, - NodeID1: info.NodeKey1Bytes, - NodeID2: info.NodeKey2Bytes, - ChainHash: info.ChainHash, - BitcoinKey1: info.BitcoinKey1Bytes, - Features: lnwire.NewRawFeatureVector(), - BitcoinKey2: info.BitcoinKey2Bytes, + ShortChannelID: chanID, + NodeID1: info.NodeKey1Bytes, + NodeID2: info.NodeKey2Bytes, + ChainHash: info.ChainHash, + BitcoinKey1: info.BitcoinKey1Bytes, + Features: lnwire.NewRawFeatureVector(), + BitcoinKey2: info.BitcoinKey2Bytes, + ExtraOpaqueData: edge.ExtraOpaqueData, } chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( info.AuthProof.NodeSig1Bytes, From 88aa74be57dc99537389c8248c43ccabe1a35dee Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 20:14:17 -0700 Subject: [PATCH 07/10] discovery: add new set of tests to ensure we validate w/ extra data --- discovery/gossiper_test.go | 159 +++++++++++++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 7 deletions(-) diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index bc3642fd5..3e8c5f226 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -372,10 +372,9 @@ func createAnnouncements(blockHeight uint32) (*annBatch, error) { } func createNodeAnnouncement(priv *btcec.PrivateKey, - timestamp uint32) (*lnwire.NodeAnnouncement, - error) { - var err error + timestamp uint32, extraBytes ...[]byte) (*lnwire.NodeAnnouncement, error) { + var err error k := hex.EncodeToString(priv.Serialize()) alias, err := lnwire.NewNodeAlias("kek" + k[:10]) if err != nil { @@ -389,6 +388,9 @@ func createNodeAnnouncement(priv *btcec.PrivateKey, Features: testFeatures, } copy(a.NodeID[:], priv.PubKey().SerializeCompressed()) + if len(extraBytes) == 1 { + a.ExtraOpaqueData = extraBytes[0] + } signer := mockSigner{priv} sig, err := SignAnnouncement(&signer, priv.PubKey(), a) @@ -405,8 +407,8 @@ func createNodeAnnouncement(priv *btcec.PrivateKey, } func createUpdateAnnouncement(blockHeight uint32, flags lnwire.ChanUpdateFlag, - nodeKey *btcec.PrivateKey, timestamp uint32) (*lnwire.ChannelUpdate, - error) { + nodeKey *btcec.PrivateKey, timestamp uint32, + extraBytes ...[]byte) (*lnwire.ChannelUpdate, error) { var err error @@ -421,6 +423,9 @@ func createUpdateAnnouncement(blockHeight uint32, flags lnwire.ChanUpdateFlag, FeeRate: uint32(prand.Int31()), BaseFee: uint32(prand.Int31()), } + if len(extraBytes) == 1 { + a.ExtraOpaqueData = extraBytes[0] + } pub := nodeKey.PubKey() signer := mockSigner{nodeKey} @@ -437,7 +442,8 @@ func createUpdateAnnouncement(blockHeight uint32, flags lnwire.ChanUpdateFlag, return a, nil } -func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnouncement, error) { +func createRemoteChannelAnnouncement(blockHeight uint32, + extraBytes ...[]byte) (*lnwire.ChannelAnnouncement, error) { var err error a := &lnwire.ChannelAnnouncement{ @@ -452,6 +458,9 @@ func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnounc copy(a.NodeID2[:], nodeKeyPub2.SerializeCompressed()) copy(a.BitcoinKey1[:], bitcoinKeyPub1.SerializeCompressed()) copy(a.BitcoinKey2[:], bitcoinKeyPub2.SerializeCompressed()) + if len(extraBytes) == 1 { + a.ExtraOpaqueData = extraBytes[0] + } pub := nodeKeyPriv1.PubKey() signer := mockSigner{nodeKeyPriv1} @@ -1736,7 +1745,6 @@ func TestDeDuplicatedAnnouncements(t *testing.T) { // same channel ID. Adding this shouldn't cause an increase in the // number of items as they should be de-duplicated. ca2, err := createRemoteChannelAnnouncement(0) - if err != nil { t.Fatalf("can't create remote channel announcement: %v", err) } @@ -2146,6 +2154,143 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { } } +// TestExtraDataChannelAnnouncementValidation tests that we're able to properly +// validate a ChannelAnnouncement that includes opaque bytes that we don't +// currently know of. +func TestExtraDataChannelAnnouncementValidation(t *testing.T) { + t.Parallel() + + ctx, cleanup, err := createTestCtx(0) + if err != nil { + t.Fatalf("can't create context: %v", err) + } + defer cleanup() + + remotePeer := &mockPeer{nodeKeyPriv1.PubKey(), nil, nil} + + // We'll now create an announcement that contains an extra set of bytes + // that we don't know of ourselves, but should still include in the + // final signature check. + extraBytes := []byte("gotta validate this stil!") + ca, err := createRemoteChannelAnnouncement(0, extraBytes) + if err != nil { + t.Fatalf("can't create channel announcement: %v", err) + } + + // We'll now send the announcement to the main gossiper. We should be + // able to validate this announcement to problem. + select { + case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, remotePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } + if err != nil { + t.Fatalf("unable to process :%v", err) + } +} + +// TestExtraDataChannelUpdateValidation tests that we're able to properly +// validate a ChannelUpdate that includes opaque bytes that we don't currently +// know of. +func TestExtraDataChannelUpdateValidation(t *testing.T) { + t.Parallel() + + ctx, cleanup, err := createTestCtx(0) + if err != nil { + t.Fatalf("can't create context: %v", err) + } + defer cleanup() + + remotePeer := &mockPeer{nodeKeyPriv1.PubKey(), nil, nil} + timestamp := uint32(123456) + + // In this scenario, we'll create two announcements, one regular + // channel announcement, and another channel update announcement, that + // has additional data that we won't be interpreting. + chanAnn, err := createRemoteChannelAnnouncement(0) + if err != nil { + t.Fatalf("unable to create chan ann: %v", err) + } + chanUpdAnn1, err := createUpdateAnnouncement( + 0, 0, nodeKeyPriv1, timestamp, + []byte("must also validate"), + ) + if err != nil { + t.Fatalf("unable to create chan up: %v", err) + } + chanUpdAnn2, err := createUpdateAnnouncement( + 0, 1, nodeKeyPriv2, timestamp, + []byte("must also validate"), + ) + if err != nil { + t.Fatalf("unable to create chan up: %v", err) + } + + // We should be able to properly validate all three messages without + // any issue. + select { + case err = <-ctx.gossiper.ProcessRemoteAnnouncement(chanAnn, remotePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } + if err != nil { + t.Fatalf("unable to process announcement: %v", err) + } + + select { + case err = <-ctx.gossiper.ProcessRemoteAnnouncement(chanUpdAnn1, remotePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } + if err != nil { + t.Fatalf("unable to process announcement: %v", err) + } + + select { + case err = <-ctx.gossiper.ProcessRemoteAnnouncement(chanUpdAnn2, remotePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } + if err != nil { + t.Fatalf("unable to process announcement: %v", err) + } +} + +// TestExtraDataNodeAnnouncementValidation tests that we're able to properly +// validate a NodeAnnouncement that includes opaque bytes that we don't +// currently know of. +func TestExtraDataNodeAnnouncementValidation(t *testing.T) { + t.Parallel() + + ctx, cleanup, err := createTestCtx(0) + if err != nil { + t.Fatalf("can't create context: %v", err) + } + defer cleanup() + + remotePeer := &mockPeer{nodeKeyPriv1.PubKey(), nil, nil} + timestamp := uint32(123456) + + // We'll create a node announcement that includes a set of opaque data + // which we don't know of, but will store anyway in order to ensure + // upgrades can flow smoothly in the future. + nodeAnn, err := createNodeAnnouncement( + nodeKeyPriv1, timestamp, []byte("gotta validate"), + ) + if err != nil { + t.Fatalf("can't create node announcement: %v", err) + } + + select { + case err = <-ctx.gossiper.ProcessRemoteAnnouncement(nodeAnn, remotePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } + if err != nil { + t.Fatalf("unable to process announcement: %v", err) + } +} + // mockPeer implements the lnpeer.Peer interface and is used to test the // gossiper's interaction with peers. type mockPeer struct { From 780828824916a6ff24e70d34f0a68a664db7aff5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 20:15:00 -0700 Subject: [PATCH 08/10] chanseries+server: populate ExtraOpaqueData field when reading from disk --- chan_series.go | 17 ++++++++++------- server.go | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/chan_series.go b/chan_series.go index 0088c7ec4..ecc29ee22 100644 --- a/chan_series.go +++ b/chan_series.go @@ -159,13 +159,14 @@ func makeNodeAnn(n *channeldb.LightningNode) (*lnwire.NodeAnnouncement, error) { return nil, err } return &lnwire.NodeAnnouncement{ - Signature: wireSig, - Timestamp: uint32(n.LastUpdate.Unix()), - Addresses: n.Addresses, - NodeID: n.PubKeyBytes, - Features: n.Features.RawFeatureVector, - RGBColor: n.Color, - Alias: alias, + Signature: wireSig, + Timestamp: uint32(n.LastUpdate.Unix()), + Addresses: n.Addresses, + NodeID: n.PubKeyBytes, + Features: n.Features.RawFeatureVector, + RGBColor: n.Color, + Alias: alias, + ExtraOpaqueData: n.ExtraOpaqueData, }, nil } @@ -277,6 +278,7 @@ func (c *chanSeries) FetchChanUpdates(chain chainhash.Hash, HtlcMinimumMsat: e1.MinHTLC, BaseFee: uint32(e1.FeeBaseMSat), FeeRate: uint32(e1.FeeProportionalMillionths), + ExtraOpaqueData: e1.ExtraOpaqueData, } chanUpdate.Signature, err = lnwire.NewSigFromRawSignature(e1.SigBytes) if err != nil { @@ -295,6 +297,7 @@ func (c *chanSeries) FetchChanUpdates(chain chainhash.Hash, HtlcMinimumMsat: e2.MinHTLC, BaseFee: uint32(e2.FeeBaseMSat), FeeRate: uint32(e2.FeeProportionalMillionths), + ExtraOpaqueData: e1.ExtraOpaqueData, } chanUpdate.Signature, err = lnwire.NewSigFromRawSignature(e2.SigBytes) if err != nil { diff --git a/server.go b/server.go index 598cd7c3e..36fcc484d 100644 --- a/server.go +++ b/server.go @@ -3015,6 +3015,7 @@ func createChannelUpdate(info *channeldb.ChannelEdgeInfo, HtlcMinimumMsat: policy.MinHTLC, BaseFee: uint32(policy.FeeBaseMSat), FeeRate: uint32(policy.FeeProportionalMillionths), + ExtraOpaqueData: policy.ExtraOpaqueData, } var err error From 48072f8e826d9a08305ff7b53878641f463d4478 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 4 Sep 2018 17:01:20 -0700 Subject: [PATCH 09/10] channeldb: add limit on the max number of opaque bytes per announcement In this commit, we add a new limit on the largest number of extra opaque bytes that we'll allow to be written per vertex/edge. We do this in order to limit the amount of disk space that we expose, as it's possible that nodes may start to pad their announcements adding an additional externalized cost as nodes may need to continue to store and relay these large announcements. --- channeldb/error.go | 10 ++++++++++ channeldb/graph.go | 29 ++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/channeldb/error.go b/channeldb/error.go index 5c8e20982..e4df0a56c 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -94,3 +94,13 @@ var ( // to the log not having any recorded events. ErrNoForwardingEvents = fmt.Errorf("no recorded forwarding events") ) + +// ErrTooManyExtraOpaqueBytes creates an error which should be returned if the +// caller attempts to write an announcement message which bares too many extra +// opaque bytes. We limit this value in order to ensure that we don't waste +// disk space due to nodes unnecessarily padding out their announcements with +// garbage data. +func ErrTooManyExtraOpaqueBytes(numBytes int) error { + return fmt.Errorf("max allowed number of opaque bytes is %v, received "+ + "%v bytes", MaxAllowedExtraOpaqueBytes, numBytes) +} diff --git a/channeldb/graph.go b/channeldb/graph.go index 340cc24df..8f56d8ee9 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -127,6 +127,14 @@ var ( nodeBloomKey = []byte("node-bloom") ) +const ( + // MaxAllowedExtraOpaqueBytes is the largest amount of opaque bytes that + // we'll permit to be written to disk. We limit this as otherwise, it + // would be possible for a node to create a ton of updates and slowly + // fill our disk, and also waste bandwidth due to relaying. + MaxAllowedExtraOpaqueBytes = 10000 +) + // ChannelGraph is a persistent, on-disk graph representation of the Lightning // Network. This struct can be used to implement path finding algorithms on top // of, and also to update a node's view based on information received from the @@ -2715,6 +2723,9 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket, return err } + if len(node.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes { + return ErrTooManyExtraOpaqueBytes(len(node.ExtraOpaqueData)) + } err = wire.WriteVarBytes(&b, 0, node.ExtraOpaqueData) if err != nil { return err @@ -2846,7 +2857,9 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) { // We'll try and see if there are any opaque bytes left, if not, then // we'll ignore the EOF error and return the node as is. - node.ExtraOpaqueData, err = wire.ReadVarBytes(r, 0, 1000, "blob") + node.ExtraOpaqueData, err = wire.ReadVarBytes( + r, 0, MaxAllowedExtraOpaqueBytes, "blob", + ) if err != nil && err != io.ErrUnexpectedEOF { return LightningNode{}, err } @@ -2909,6 +2922,9 @@ func putChanEdgeInfo(edgeIndex *bolt.Bucket, edgeInfo *ChannelEdgeInfo, chanID [ return err } + if len(edgeInfo.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes { + return ErrTooManyExtraOpaqueBytes(len(edgeInfo.ExtraOpaqueData)) + } err := wire.WriteVarBytes(&b, 0, edgeInfo.ExtraOpaqueData) if err != nil { return err @@ -2993,7 +3009,9 @@ func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) { // We'll try and see if there are any opaque bytes left, if not, then // we'll ignore the EOF error and return the edge as is. - edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes(r, 0, 1000, "blob") + edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes( + r, 0, MaxAllowedExtraOpaqueBytes, "blob", + ) if err != nil && err != io.ErrUnexpectedEOF { return ChannelEdgeInfo{}, err } @@ -3046,6 +3064,9 @@ func putChanEdgePolicy(edges, nodes *bolt.Bucket, edge *ChannelEdgePolicy, return err } + if len(edge.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes { + return ErrTooManyExtraOpaqueBytes(len(edge.ExtraOpaqueData)) + } if err := wire.WriteVarBytes(&b, 0, edge.ExtraOpaqueData); err != nil { return err } @@ -3236,7 +3257,9 @@ func deserializeChanEdgePolicy(r io.Reader, // We'll try and see if there are any opaque bytes left, if not, then // we'll ignore the EOF error and return the edge as is. - edge.ExtraOpaqueData, err = wire.ReadVarBytes(r, 0, 1000, "blob") + edge.ExtraOpaqueData, err = wire.ReadVarBytes( + r, 0, MaxAllowedExtraOpaqueBytes, "blob", + ) if err != nil && err != io.ErrUnexpectedEOF { return nil, err } From 785efcfaa21c49ee4bd06982b4217f30c57af2e5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 5 Sep 2018 16:42:16 -0700 Subject: [PATCH 10/10] channeldb: also ignore the EOF error when trying to read ExtraOpaqueBytes In this commit, we account for the additional case wherein the announcement hasn't yet been written with the extra zero byte to indicate that there aren't any remaining bytes to be read. Before this commit, we accounted for the case where the announcement was written with the extra byte, but now we ensure that legacy nodes that upgrade will be able to boot properly. --- channeldb/graph.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/channeldb/graph.go b/channeldb/graph.go index 8f56d8ee9..63f04befd 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -2860,7 +2860,10 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) { node.ExtraOpaqueData, err = wire.ReadVarBytes( r, 0, MaxAllowedExtraOpaqueBytes, "blob", ) - if err != nil && err != io.ErrUnexpectedEOF { + switch { + case err == io.ErrUnexpectedEOF: + case err == io.EOF: + case err != nil: return LightningNode{}, err } @@ -3012,7 +3015,10 @@ func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) { edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes( r, 0, MaxAllowedExtraOpaqueBytes, "blob", ) - if err != nil && err != io.ErrUnexpectedEOF { + switch { + case err == io.ErrUnexpectedEOF: + case err == io.EOF: + case err != nil: return ChannelEdgeInfo{}, err } @@ -3260,7 +3266,10 @@ func deserializeChanEdgePolicy(r io.Reader, edge.ExtraOpaqueData, err = wire.ReadVarBytes( r, 0, MaxAllowedExtraOpaqueBytes, "blob", ) - if err != nil && err != io.ErrUnexpectedEOF { + switch { + case err == io.ErrUnexpectedEOF: + case err == io.EOF: + case err != nil: return nil, err }