lnwire: add support for Features in NodeAnnouncement

Add support for Features in NodeAnnouncment according to spec.
This commit is contained in:
bryanvu 2017-03-20 02:24:55 -07:00 committed by Olaoluwa Osuntokun
parent 3087bec4db
commit 085b7333cb
10 changed files with 145 additions and 32 deletions

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
@ -830,6 +831,9 @@ type LightningNode struct {
// TODO(roasbeef): hook into serialization once full verification is in // TODO(roasbeef): hook into serialization once full verification is in
AuthSig *btcec.Signature AuthSig *btcec.Signature
// Features is the list of protocol features supported by this node.
Features *lnwire.FeatureVector
db *DB db *DB
// TODO(roasbeef): discovery will need storage to keep it's last IP // 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 return err
} }
if err := node.Features.Encode(&b); err != nil {
return err
}
numAddresses := uint16(len(node.Addresses)) numAddresses := uint16(len(node.Addresses))
byteOrder.PutUint16(scratch[:2], numAddresses) byteOrder.PutUint16(scratch[:2], numAddresses)
if _, err := b.Write(scratch[:2]); err != nil { if _, err := b.Write(scratch[:2]); err != nil {
@ -1395,6 +1403,11 @@ func deserializeLightningNode(r io.Reader) (*LightningNode, error) {
return nil, err return nil, err
} }
node.Features, err = lnwire.NewFeatureVectorFromReader(r)
if err != nil {
return nil, err
}
if _, err := r.Read(scratch[:2]); err != nil { if _, err := r.Read(scratch[:2]); err != nil {
return nil, err return nil, err
} }

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
@ -35,6 +36,8 @@ var (
} }
_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
testFeatures = lnwire.NewFeatureVector([]lnwire.Feature{})
) )
func createTestVertex(db *DB) (*LightningNode, error) { func createTestVertex(db *DB) (*LightningNode, error) {
@ -48,10 +51,11 @@ func createTestVertex(db *DB) (*LightningNode, error) {
pub := priv.PubKey().SerializeCompressed() pub := priv.PubKey().SerializeCompressed()
return &LightningNode{ return &LightningNode{
LastUpdate: time.Unix(updateTime, 0), LastUpdate: time.Unix(updateTime, 0),
Addresses: testAddrs,
PubKey: priv.PubKey(), PubKey: priv.PubKey(),
Color: color.RGBA{1, 2, 3, 0}, Color: color.RGBA{1, 2, 3, 0},
Alias: "kek" + string(pub[:]), Alias: "kek" + string(pub[:]),
Features: testFeatures,
Addresses: testAddrs,
db: db, db: db,
}, nil }, nil
} }
@ -70,10 +74,11 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:]) _, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{ node := &LightningNode{
LastUpdate: time.Unix(1232342, 0), LastUpdate: time.Unix(1232342, 0),
Addresses: testAddrs,
PubKey: testPub, PubKey: testPub,
Color: color.RGBA{1, 2, 3, 0}, Color: color.RGBA{1, 2, 3, 0},
Alias: "kek", Alias: "kek",
Features: testFeatures,
Addresses: testAddrs,
db: db, db: db,
} }
@ -97,9 +102,8 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
} }
// The two nodes should match exactly! // The two nodes should match exactly!
if !reflect.DeepEqual(node, dbNode) { if err := compareNodes(node, dbNode); err != nil {
t.Fatalf("retrieved node doesn't match: expected %#v\n, got %#v\n", t.Fatalf("nodes don't match: %v", err)
node, dbNode)
} }
// Next, delete the node from the graph, this should purge all data // Next, delete the node from the graph, this should purge all data
@ -194,9 +198,8 @@ func TestSourceNode(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
if !reflect.DeepEqual(testNode, sourceNode) { if err := compareNodes(testNode, sourceNode); err != nil {
t.Fatalf("nodes don't match, expected %#v \n got %#v", t.Fatalf("nodes don't match: %v", err)
testNode, sourceNode)
} }
} }
@ -454,13 +457,11 @@ func TestEdgeInfoUpdates(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch channel by ID: %v", err) t.Fatalf("unable to fetch channel by ID: %v", err)
} }
if !reflect.DeepEqual(dbEdge1, edge1) { if err := compareEdgePolicies(dbEdge1, edge1); err != nil {
t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge1, t.Fatalf("edge doesn't match: %v", err)
dbEdge1)
} }
if !reflect.DeepEqual(dbEdge2, edge2) { if err := compareEdgePolicies(dbEdge2, edge2); err != nil {
t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge2, t.Fatalf("edge doesn't match: %v", err)
dbEdge2)
} }
assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo)
@ -470,13 +471,11 @@ func TestEdgeInfoUpdates(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch channel by ID: %v", err) t.Fatalf("unable to fetch channel by ID: %v", err)
} }
if !reflect.DeepEqual(dbEdge1, edge1) { if err := compareEdgePolicies(dbEdge1, edge1); err != nil {
t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge1, t.Fatalf("edge doesn't match: %v", err)
dbEdge1)
} }
if !reflect.DeepEqual(dbEdge2, edge2) { if err := compareEdgePolicies(dbEdge2, edge2); err != nil {
t.Fatalf("edge doesn't match: expected %#v, \n got %#v", edge2, t.Fatalf("edge doesn't match: %v", err)
dbEdge2)
} }
assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo)
} }
@ -830,3 +829,77 @@ func TestGraphPruning(t *testing.T) {
assertPruneTip(t, graph, &blockHash, blockHeight) assertPruneTip(t, graph, &blockHash, blockHeight)
asserNumChans(t, graph, 0) 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
}

View File

@ -2084,7 +2084,7 @@ func testNodeAnnouncement(net *networkHarness, t *harnessTest) {
} }
var lndArgs []string var lndArgs []string
for address, _ := range ipAddresses { for address := range ipAddresses {
lndArgs = append(lndArgs, "--externalip="+address) lndArgs = append(lndArgs, "--externalip="+address)
} }

View File

@ -75,4 +75,9 @@ var (
green: 255, green: 255,
blue: 255, blue: 255,
} }
someFeature = featureName("somefeature")
someFeatures = NewFeatureVector([]Feature{
{someFeature, OptionalFlag},
})
) )

View File

@ -97,6 +97,9 @@ type NodeAnnouncement struct {
// Alias is used to customize their node's appearance in maps and graphs // Alias is used to customize their node's appearance in maps and graphs
Alias Alias Alias Alias
// Features is the list of protocol features this node supports.
Features *FeatureVector
// Address includes two specification fields: 'ipv6' and 'port' on which // Address includes two specification fields: 'ipv6' and 'port' on which
// the node is accepting incoming connections. // the node is accepting incoming connections.
Addresses []net.Addr Addresses []net.Addr
@ -127,6 +130,7 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
&a.RGBColor, &a.RGBColor,
&a.Alias, &a.Alias,
&a.Addresses, &a.Addresses,
&a.Features,
) )
} }
@ -141,6 +145,7 @@ func (a *NodeAnnouncement) Encode(w io.Writer, pver uint32) error {
a.RGBColor, a.RGBColor,
a.Alias, a.Alias,
a.Addresses, a.Addresses,
a.Features,
) )
} }
@ -162,6 +167,7 @@ func (a *NodeAnnouncement) MaxPayloadLength(pver uint32) uint32 {
// NodeID - 33 bytes // NodeID - 33 bytes
// RGBColor - 3 bytes // RGBColor - 3 bytes
// Alias - 32 bytes // Alias - 32 bytes
// Features - variable
// NumAddresses - 2 bytes // NumAddresses - 2 bytes
// AddressDescriptor - 1 byte // AddressDescriptor - 1 byte
// Ipv4 - 4 bytes (optional) // Ipv4 - 4 bytes (optional)
@ -183,6 +189,7 @@ func (a *NodeAnnouncement) DataToSign() ([]byte, error) {
a.RGBColor, a.RGBColor,
a.Alias, a.Alias,
a.Addresses, a.Addresses,
a.Features,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -10,31 +10,37 @@ import (
) )
func TestNodeAnnouncementEncodeDecode(t *testing.T) { func TestNodeAnnouncementEncodeDecode(t *testing.T) {
cua := &NodeAnnouncement{ na := &NodeAnnouncement{
Signature: someSig, Signature: someSig,
Timestamp: maxUint32, Timestamp: maxUint32,
NodeID: pubKey, NodeID: pubKey,
RGBColor: someRGB, RGBColor: someRGB,
Alias: someAlias, Alias: someAlias,
Addresses: someAddresses, Addresses: someAddresses,
Features: someFeatures,
} }
// Next encode the NA message into an empty bytes buffer. // Next encode the NA message into an empty bytes buffer.
var b 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) t.Fatalf("unable to encode NodeAnnouncement: %v", err)
} }
// Deserialize the encoded NA message into a new empty struct. // Deserialize the encoded NA message into a new empty struct.
cua2 := &NodeAnnouncement{} na2 := &NodeAnnouncement{}
if err := cua2.Decode(&b, 0); err != nil { if err := na2.Decode(&b, 0); err != nil {
t.Fatalf("unable to decode NodeAnnouncement: %v", err) 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. // 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", 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, NodeID: nodePubKey,
RGBColor: someRGB, RGBColor: someRGB,
Alias: someAlias, Alias: someAlias,
Features: someFeatures,
} }
dataToSign, _ := na.DataToSign() dataToSign, _ := na.DataToSign()
@ -73,6 +80,7 @@ func TestNodeAnnouncementPayloadLength(t *testing.T) {
RGBColor: someRGB, RGBColor: someRGB,
Alias: someAlias, Alias: someAlias,
Addresses: someAddresses, Addresses: someAddresses,
Features: someFeatures,
} }
var b bytes.Buffer var b bytes.Buffer
@ -81,9 +89,9 @@ func TestNodeAnnouncementPayloadLength(t *testing.T) {
} }
serializedLength := uint32(b.Len()) serializedLength := uint32(b.Len())
if serializedLength != 164 { if serializedLength != 167 {
t.Fatalf("payload length estimate is incorrect: expected %v "+ t.Fatalf("payload length estimate is incorrect: expected %v "+
"got %v", 164, serializedLength) "got %v", 167, serializedLength)
} }
if na.MaxPayloadLength(0) != 8192 { if na.MaxPayloadLength(0) != 8192 {

View File

@ -24,6 +24,8 @@ var (
Port: 9000} Port: 9000}
testAddrs = []net.Addr{testAddr} testAddrs = []net.Addr{testAddr}
testFeatures = lnwire.NewFeatureVector([]lnwire.Feature{})
testHash = [32]byte{ testHash = [32]byte{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
@ -47,6 +49,7 @@ func createGraphNode() (*channeldb.LightningNode, error) {
PubKey: priv.PubKey(), PubKey: priv.PubKey(),
Color: color.RGBA{1, 2, 3, 0}, Color: color.RGBA{1, 2, 3, 0},
Alias: "kek" + string(pub[:]), Alias: "kek" + string(pub[:]),
Features: testFeatures,
}, nil }, nil
} }
@ -68,6 +71,7 @@ func createTestWireNode() (*lnwire.NodeAnnouncement, error) {
Addresses: testAddrs, Addresses: testAddrs,
NodeID: priv.PubKey(), NodeID: priv.PubKey(),
Alias: alias, Alias: alias,
Features: testFeatures,
}, nil }, nil
} }

View File

@ -167,6 +167,7 @@ func parseTestGraph(path string) (*channeldb.ChannelGraph, func(), aliasMap, err
Addresses: testAddrs, Addresses: testAddrs,
PubKey: pub, PubKey: pub,
Alias: node.Alias, Alias: node.Alias,
Features: testFeatures,
} }
// We require all aliases within the graph to be unique for our // We require all aliases within the graph to be unique for our

View File

@ -696,6 +696,7 @@ func (r *ChannelRouter) processNetworkAnnouncement(msg lnwire.Message) bool {
Addresses: msg.Addresses, Addresses: msg.Addresses,
PubKey: msg.NodeID, PubKey: msg.NodeID,
Alias: msg.Alias.String(), Alias: msg.Alias.String(),
Features: msg.Features,
} }
if err = r.cfg.Graph.AddLightningNode(node); err != nil { if err = r.cfg.Graph.AddLightningNode(node); err != nil {
@ -945,9 +946,10 @@ func (r *ChannelRouter) syncChannelGraph(syncReq *syncRequest) error {
ann := &lnwire.NodeAnnouncement{ ann := &lnwire.NodeAnnouncement{
Signature: r.fakeSig, Signature: r.fakeSig,
Timestamp: uint32(node.LastUpdate.Unix()), Timestamp: uint32(node.LastUpdate.Unix()),
Addresses: node.Addresses,
NodeID: node.PubKey, NodeID: node.PubKey,
Alias: alias, Alias: alias,
Features: node.Features,
Addresses: node.Addresses,
} }
announceMessages = append(announceMessages, ann) announceMessages = append(announceMessages, ann)

View File

@ -171,7 +171,8 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
Addresses: selfAddrs, Addresses: selfAddrs,
PubKey: privKey.PubKey(), PubKey: privKey.PubKey(),
// TODO(roasbeef): make alias configurable // TODO(roasbeef): make alias configurable
Alias: hex.EncodeToString(serializedPubKey[:10]), Alias: hex.EncodeToString(serializedPubKey[:10]),
Features: globalFeatures,
} }
if err := chanGraph.SetSourceNode(self); err != nil { if err := chanGraph.SetSourceNode(self); err != nil {
return nil, err return nil, err
@ -510,7 +511,6 @@ func (s *server) inboundPeerConnected(conn net.Conn) {
for _, connReq := range connReqs { for _, connReq := range connReqs {
s.connMgr.Remove(connReq.ID()) s.connMgr.Remove(connReq.ID())
} }
delete(s.persistentConnReqs, pubStr)
} }
s.pendingConnMtx.RUnlock() s.pendingConnMtx.RUnlock()