diff --git a/chan_series.go b/chan_series.go index 0088c7ec4e..ecc29ee22c 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/channeldb/error.go b/channeldb/error.go index 5c8e209821..e4df0a56c2 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 1d7625ba70..63f04befdb 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 @@ -1694,6 +1702,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 +1994,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 +2346,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 +2723,14 @@ 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 + } + if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil { return err } @@ -2815,6 +2855,18 @@ 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, MaxAllowedExtraOpaqueBytes, "blob", + ) + switch { + case err == io.ErrUnexpectedEOF: + case err == io.EOF: + case err != nil: + return LightningNode{}, err + } + return node, nil } @@ -2873,6 +2925,14 @@ 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 + } + return edgeIndex.Put(chanID[:], b.Bytes()) } @@ -2950,6 +3010,18 @@ 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, MaxAllowedExtraOpaqueBytes, "blob", + ) + switch { + case err == io.ErrUnexpectedEOF: + case err == io.EOF: + case err != nil: + return ChannelEdgeInfo{}, err + } + return edgeInfo, nil } @@ -2998,6 +3070,13 @@ 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 + } + // 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 +3261,18 @@ 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, MaxAllowedExtraOpaqueBytes, "blob", + ) + switch { + case err == io.ErrUnexpectedEOF: + case err == io.EOF: + case err != nil: + return nil, err + } + edge.Node = &node return edge, nil } diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index 4e1d10f718..abcde9ef77 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 } diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 5f3b892732..dc4d2b0eed 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, diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index bc3642fd54..3e8c5f2260 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 { diff --git a/discovery/utils.go b/discovery/utils.go index 0cbc94708e..63143c86c0 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 { diff --git a/lnwire/announcement_signatures.go b/lnwire/announcement_signatures.go index 748f29b126..fd82211c3b 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 f9b86510b6..70f532419a 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 971f7bb3dd..5cc3b43083 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/lnwire_test.go b/lnwire/lnwire_test.go index d67fc9e7ec..b30184bc3b 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) { diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go index 3a0b7ff9b1..f0ed70ea6c 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 9b2ab10194..d59281a28b 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)) } } } diff --git a/server.go b/server.go index 598cd7c3ea..36fcc484d9 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