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"
"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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -75,4 +75,9 @@ var (
green: 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 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

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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()