diff --git a/channeldb/graph.go b/channeldb/graph.go index cacc714ea..34f762f02 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -9,6 +9,7 @@ import ( "time" "github.com/boltdb/bolt" + "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" @@ -830,6 +831,9 @@ type LightningNode struct { // TODO(roasbeef): hook into serialization once full verification is in AuthSig *btcec.Signature + // Features is the list of protocol features supported by this node. + Features *lnwire.FeatureVector + db *DB // TODO(roasbeef): discovery will need storage to keep it's last IP @@ -1309,6 +1313,10 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket, node *L return err } + if err := node.Features.Encode(&b); err != nil { + return err + } + numAddresses := uint16(len(node.Addresses)) byteOrder.PutUint16(scratch[:2], numAddresses) if _, err := b.Write(scratch[:2]); err != nil { @@ -1395,6 +1403,11 @@ func deserializeLightningNode(r io.Reader) (*LightningNode, error) { return nil, err } + node.Features, err = lnwire.NewFeatureVectorFromReader(r) + if err != nil { + return nil, err + } + if _, err := r.Read(scratch[:2]); err != nil { return nil, err } diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index e8170f306..85db3a27b 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -14,6 +14,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" @@ -35,6 +36,8 @@ var ( } _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + + testFeatures = lnwire.NewFeatureVector([]lnwire.Feature{}) ) func createTestVertex(db *DB) (*LightningNode, error) { @@ -48,10 +51,11 @@ func createTestVertex(db *DB) (*LightningNode, error) { pub := priv.PubKey().SerializeCompressed() return &LightningNode{ LastUpdate: time.Unix(updateTime, 0), - Addresses: testAddrs, PubKey: priv.PubKey(), Color: color.RGBA{1, 2, 3, 0}, Alias: "kek" + string(pub[:]), + Features: testFeatures, + Addresses: testAddrs, db: db, }, nil } @@ -70,10 +74,11 @@ func TestNodeInsertionAndDeletion(t *testing.T) { _, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:]) node := &LightningNode{ LastUpdate: time.Unix(1232342, 0), - Addresses: testAddrs, PubKey: testPub, Color: color.RGBA{1, 2, 3, 0}, Alias: "kek", + Features: testFeatures, + Addresses: testAddrs, db: db, } @@ -97,9 +102,8 @@ func TestNodeInsertionAndDeletion(t *testing.T) { } // The two nodes should match exactly! - if !reflect.DeepEqual(node, dbNode) { - t.Fatalf("retrieved node doesn't match: expected %#v\n, got %#v\n", - node, dbNode) + if err := compareNodes(node, dbNode); err != nil { + t.Fatalf("nodes don't match: %v", err) } // Next, delete the node from the graph, this should purge all data @@ -194,9 +198,8 @@ func TestSourceNode(t *testing.T) { if err != nil { t.Fatalf("unable to fetch source node: %v", err) } - if !reflect.DeepEqual(testNode, sourceNode) { - t.Fatalf("nodes don't match, expected %#v \n got %#v", - testNode, sourceNode) + if err := compareNodes(testNode, sourceNode); err != nil { + t.Fatalf("nodes don't match: %v", err) } } @@ -454,13 +457,11 @@ func TestEdgeInfoUpdates(t *testing.T) { if err != nil { t.Fatalf("unable to fetch channel by ID: %v", err) } - if !reflect.DeepEqual(dbEdge1, edge1) { - t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge1, - dbEdge1) + if err := compareEdgePolicies(dbEdge1, edge1); err != nil { + t.Fatalf("edge doesn't match: %v", err) } - if !reflect.DeepEqual(dbEdge2, edge2) { - t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge2, - dbEdge2) + if err := compareEdgePolicies(dbEdge2, edge2); err != nil { + t.Fatalf("edge doesn't match: %v", err) } assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) @@ -470,13 +471,11 @@ func TestEdgeInfoUpdates(t *testing.T) { if err != nil { t.Fatalf("unable to fetch channel by ID: %v", err) } - if !reflect.DeepEqual(dbEdge1, edge1) { - t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge1, - dbEdge1) + if err := compareEdgePolicies(dbEdge1, edge1); err != nil { + t.Fatalf("edge doesn't match: %v", err) } - if !reflect.DeepEqual(dbEdge2, edge2) { - t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge2, - dbEdge2) + if err := compareEdgePolicies(dbEdge2, edge2); err != nil { + t.Fatalf("edge doesn't match: %v", err) } assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) } @@ -830,3 +829,77 @@ func TestGraphPruning(t *testing.T) { assertPruneTip(t, graph, &blockHash, blockHeight) asserNumChans(t, graph, 0) } + +// compareNodes is used to compare two LightningNodes while excluding the +// Features struct, which cannot be compared as the semantics for reserializing +// the featuresMap have not been defined. +func compareNodes(a, b *LightningNode) error { + if !reflect.DeepEqual(a.LastUpdate, b.LastUpdate) { + return fmt.Errorf("LastUpdate doesn't match: expected %#v, \n"+ + "got %#v", a.LastUpdate, b.LastUpdate) + } + if !reflect.DeepEqual(a.Addresses, b.Addresses) { + return fmt.Errorf("Addresses doesn't match: expected %#v, \n "+ + "got %#v", a.Addresses, b.Addresses) + } + if !reflect.DeepEqual(a.PubKey, b.PubKey) { + return fmt.Errorf("PubKey doesn't match: expected %#v, \n "+ + "got %#v", a.PubKey, b.PubKey) + } + if !reflect.DeepEqual(a.Color, b.Color) { + return fmt.Errorf("Color doesn't match: expected %#v, \n "+ + "got %#v", a.Color, b.Color) + } + if !reflect.DeepEqual(a.Alias, b.Alias) { + return fmt.Errorf("Alias doesn't match: expected %#v, \n "+ + "got %#v", a.Alias, b.Alias) + } + if !reflect.DeepEqual(a.db, b.db) { + return fmt.Errorf("db doesn't match: expected %#v, \n "+ + "got %#v", a.db, b.db) + } + + return nil +} + +// compareEdgePolicies is used to compare two ChannelEdgePolices using +// compareNodes, so as to exclude comparisons of the Nodes' Features struct. +func compareEdgePolicies(a, b *ChannelEdgePolicy) error { + if a.ChannelID != b.ChannelID { + return fmt.Errorf("ChannelID doesn't match: expected %v, "+ + "got %v", a.ChannelID, b.ChannelID) + } + if !reflect.DeepEqual(a.LastUpdate, b.LastUpdate) { + return fmt.Errorf("LastUpdate doesn't match: expected %#v, \n "+ + "got %#v", a.LastUpdate, b.LastUpdate) + } + if a.Flags != b.Flags { + return fmt.Errorf("Flags doesn't match: expected %v, "+ + "got %v", a.Flags, b.Flags) + } + if a.TimeLockDelta != b.TimeLockDelta { + return fmt.Errorf("TimeLockDelta doesn't match: expected %v, "+ + "got %v", a.TimeLockDelta, b.TimeLockDelta) + } + if a.MinHTLC != b.MinHTLC { + return fmt.Errorf("MinHTLC doesn't match: expected %v, "+ + "got %v", a.MinHTLC, b.MinHTLC) + } + if a.FeeBaseMSat != b.FeeBaseMSat { + return fmt.Errorf("FeeBaseMSat doesn't match: expected %v, "+ + "got %v", a.FeeBaseMSat, b.FeeBaseMSat) + } + if a.FeeProportionalMillionths != b.FeeProportionalMillionths { + return fmt.Errorf("FeeProportionalMillionths doesn't match: "+ + "expected %v, got %v", a.FeeProportionalMillionths, + b.FeeProportionalMillionths) + } + if err := compareNodes(a.Node, b.Node); err != nil { + return err + } + if !reflect.DeepEqual(a.db, b.db) { + return fmt.Errorf("db doesn't match: expected %#v, \n "+ + "got %#v", a.db, b.db) + } + return nil +} diff --git a/lnd_test.go b/lnd_test.go index 77b2f36f3..6f2ab9cdf 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -2084,7 +2084,7 @@ func testNodeAnnouncement(net *networkHarness, t *harnessTest) { } var lndArgs []string - for address, _ := range ipAddresses { + for address := range ipAddresses { lndArgs = append(lndArgs, "--externalip="+address) } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index ca3cd57c1..6f7097a48 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -75,4 +75,9 @@ var ( green: 255, blue: 255, } + + someFeature = featureName("somefeature") + someFeatures = NewFeatureVector([]Feature{ + {someFeature, OptionalFlag}, + }) ) diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go index d9d3eace3..e52485414 100644 --- a/lnwire/node_announcement.go +++ b/lnwire/node_announcement.go @@ -97,6 +97,9 @@ type NodeAnnouncement struct { // Alias is used to customize their node's appearance in maps and graphs Alias Alias + // Features is the list of protocol features this node supports. + Features *FeatureVector + // Address includes two specification fields: 'ipv6' and 'port' on which // the node is accepting incoming connections. Addresses []net.Addr @@ -127,6 +130,7 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { &a.RGBColor, &a.Alias, &a.Addresses, + &a.Features, ) } @@ -141,6 +145,7 @@ func (a *NodeAnnouncement) Encode(w io.Writer, pver uint32) error { a.RGBColor, a.Alias, a.Addresses, + a.Features, ) } @@ -162,6 +167,7 @@ func (a *NodeAnnouncement) MaxPayloadLength(pver uint32) uint32 { // NodeID - 33 bytes // RGBColor - 3 bytes // Alias - 32 bytes + // Features - variable // NumAddresses - 2 bytes // AddressDescriptor - 1 byte // Ipv4 - 4 bytes (optional) @@ -183,6 +189,7 @@ func (a *NodeAnnouncement) DataToSign() ([]byte, error) { a.RGBColor, a.Alias, a.Addresses, + a.Features, ) if err != nil { return nil, err diff --git a/lnwire/node_announcement_test.go b/lnwire/node_announcement_test.go index 59616cdc5..2da8a4315 100644 --- a/lnwire/node_announcement_test.go +++ b/lnwire/node_announcement_test.go @@ -10,31 +10,37 @@ import ( ) func TestNodeAnnouncementEncodeDecode(t *testing.T) { - cua := &NodeAnnouncement{ + na := &NodeAnnouncement{ Signature: someSig, Timestamp: maxUint32, NodeID: pubKey, RGBColor: someRGB, Alias: someAlias, Addresses: someAddresses, + Features: someFeatures, } // Next encode the NA message into an empty bytes buffer. var b bytes.Buffer - if err := cua.Encode(&b, 0); err != nil { + if err := na.Encode(&b, 0); err != nil { t.Fatalf("unable to encode NodeAnnouncement: %v", err) } // Deserialize the encoded NA message into a new empty struct. - cua2 := &NodeAnnouncement{} - if err := cua2.Decode(&b, 0); err != nil { + na2 := &NodeAnnouncement{} + if err := na2.Decode(&b, 0); err != nil { t.Fatalf("unable to decode NodeAnnouncement: %v", err) } + // We do not encode the feature map in feature vector, for that reason + // the node announcement messages will differ. Set feature map with nil + // in order to use deep equal function. + na.Features.featuresMap = nil + // Assert equality of the two instances. - if !reflect.DeepEqual(cua, cua2) { + if !reflect.DeepEqual(na, na2) { t.Fatalf("encode/decode error messages don't match %#v vs %#v", - cua, cua2) + na, na2) } } @@ -52,6 +58,7 @@ func TestNodeAnnouncementValidation(t *testing.T) { NodeID: nodePubKey, RGBColor: someRGB, Alias: someAlias, + Features: someFeatures, } dataToSign, _ := na.DataToSign() @@ -73,6 +80,7 @@ func TestNodeAnnouncementPayloadLength(t *testing.T) { RGBColor: someRGB, Alias: someAlias, Addresses: someAddresses, + Features: someFeatures, } var b bytes.Buffer @@ -81,9 +89,9 @@ func TestNodeAnnouncementPayloadLength(t *testing.T) { } serializedLength := uint32(b.Len()) - if serializedLength != 164 { + if serializedLength != 167 { t.Fatalf("payload length estimate is incorrect: expected %v "+ - "got %v", 164, serializedLength) + "got %v", 167, serializedLength) } if na.MaxPayloadLength(0) != 8192 { diff --git a/routing/notifications_test.go b/routing/notifications_test.go index be54abacb..e4b12876d 100644 --- a/routing/notifications_test.go +++ b/routing/notifications_test.go @@ -24,6 +24,8 @@ var ( Port: 9000} testAddrs = []net.Addr{testAddr} + testFeatures = lnwire.NewFeatureVector([]lnwire.Feature{}) + testHash = [32]byte{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, @@ -47,6 +49,7 @@ func createGraphNode() (*channeldb.LightningNode, error) { PubKey: priv.PubKey(), Color: color.RGBA{1, 2, 3, 0}, Alias: "kek" + string(pub[:]), + Features: testFeatures, }, nil } @@ -68,6 +71,7 @@ func createTestWireNode() (*lnwire.NodeAnnouncement, error) { Addresses: testAddrs, NodeID: priv.PubKey(), Alias: alias, + Features: testFeatures, }, nil } diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 6c567f7ce..de8050f77 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -167,6 +167,7 @@ func parseTestGraph(path string) (*channeldb.ChannelGraph, func(), aliasMap, err Addresses: testAddrs, PubKey: pub, Alias: node.Alias, + Features: testFeatures, } // We require all aliases within the graph to be unique for our diff --git a/routing/router.go b/routing/router.go index a677f3731..23333a11a 100644 --- a/routing/router.go +++ b/routing/router.go @@ -696,6 +696,7 @@ func (r *ChannelRouter) processNetworkAnnouncement(msg lnwire.Message) bool { Addresses: msg.Addresses, PubKey: msg.NodeID, Alias: msg.Alias.String(), + Features: msg.Features, } if err = r.cfg.Graph.AddLightningNode(node); err != nil { @@ -945,9 +946,10 @@ func (r *ChannelRouter) syncChannelGraph(syncReq *syncRequest) error { ann := &lnwire.NodeAnnouncement{ Signature: r.fakeSig, Timestamp: uint32(node.LastUpdate.Unix()), - Addresses: node.Addresses, NodeID: node.PubKey, Alias: alias, + Features: node.Features, + Addresses: node.Addresses, } announceMessages = append(announceMessages, ann) diff --git a/server.go b/server.go index 8f586ab34..3bfcca12e 100644 --- a/server.go +++ b/server.go @@ -171,7 +171,8 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, Addresses: selfAddrs, PubKey: privKey.PubKey(), // TODO(roasbeef): make alias configurable - Alias: hex.EncodeToString(serializedPubKey[:10]), + Alias: hex.EncodeToString(serializedPubKey[:10]), + Features: globalFeatures, } if err := chanGraph.SetSourceNode(self); err != nil { return nil, err @@ -510,7 +511,6 @@ func (s *server) inboundPeerConnected(conn net.Conn) { for _, connReq := range connReqs { s.connMgr.Remove(connReq.ID()) } - delete(s.persistentConnReqs, pubStr) } s.pendingConnMtx.RUnlock()